<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>Clemens Schotte</title><link>https://clemens.ms/</link><description>Clemens Schotte Blog about technology.</description><generator>Hugo -- gohugo.io</generator><language>en</language><managingEditor>clemens@navatron.com (Clemens Schotte)</managingEditor><webMaster>clemens@navatron.com (Clemens Schotte)</webMaster><copyright>Copyright by Clemens Schotte.</copyright><lastBuildDate>Wed, 18 Feb 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://clemens.ms/index.xml" rel="self" type="application/rss+xml"/><item><title>The Commodore 64 Ultimate Arrived</title><link>https://clemens.ms/c64/</link><pubDate>Tue, 16 Dec 2025 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/c64/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><p>The box arrived today, just in time before the holidays, sitting on my doorstep like a time capsule. The moment I saw the familiar Commodore logo on the packaging, I had to pause. It wasn’t just another retro gadget. It was the <strong>Commodore 64</strong>. Or at least, as close as we’re ever going to get in 2025.</p>
<p>I tore open the cardboard (carefully, because let’s be honest, I’ll probably keep the box) and there it was: the <a href="https://www.commodore.net/product-page/commodore-64-ultimate-basic-beige-batch1" target="_blank" rel="noopener noreffer "><strong>Commodore 64 Ultimate</strong></a>, in all its beige glory. The weight of it, the shape, even the slight texture of the plastic, it all felt right. Like holding a piece of my childhood again.</p>
<h2 id="a-perfect-recreation">A Perfect Recreation</h2>
<p>The first thing I noticed was the manual. A real, spiral-bound, physical manual, complete with BASIC listings. I flipped through it, running my fingers over the pages, and for a second, I was 12 years old again, typing in programs and hoping I didn’t make a typo. The attention to detail here is insane. The keyboard has that same satisfying clack (though, admittedly, with better switches than the original), the ports are all where they should be, and even the power LED glows with that same warm, slightly orange hue.</p>
<p>But this isn’t just a replica. It’s a <strong>reimagining</strong>.</p>
<p>The C64U is built around an FPGA, meaning it’s not emulation, it’s real hardware, just like the original. No software tricks, no shortcuts. It is a C64, just with a few modern luxuries. HDMI out. Wi-Fi. USB ports. A 48MHz Turbo mode (which feels almost like cheating). And yet, when I plugged in an old cartridge, it just… worked. No fuss. No compatibility issues. Just that familiar <code>LOAD &quot;*&quot;,8,1</code> prompt staring back at me, waiting for commands.</p>
<p>I connected it to my modern monitor via HDMI, but I also dug out an old CRT (Commodore 1084S monitor) from the garage, just to see. And there it was, the same slightly fuzzy, slightly wobbly image I remember from 1985. The same SID chip music, crackling through the speakers like it never left.</p>
<h2 id="the-little-things-that-matter">The Little Things That Matter</h2>
<p>The C64U comes with a USB &ldquo;cassette drive&rdquo; preloaded with 100+ games, some classics and new (<a href="https://www.commodore.net/pressplay" target="_blank" rel="noopener noreffer ">https://www.commodore.net/pressplay</a>), tools, music and even a few demos that push the hardware in ways I didn’t think possible back then.</p>
<p>And then there’s the Turbo mode. 64MHz! My 12-year-old self would have lost his mind. Games load almost instantly. BASIC programs run at speeds that feel impossible. And yet, with a flick of a switch, I can throttle it back to 1MHz, just like the old days.</p>
<h2 id="this-feels-different">This Feels Different</h2>
<p>I’ve owned a C64 in the past, but the Ultimate is something else. It’s not just about playing the games, it’s about feeling the machine again. The way the keys press down. The way the screen flickers just a little when you run a particularly aggressive <code>POKE</code> command.</p>
<p>It’s the closest I’ve ever gotten to reliving those late-night coding sessions, where I’d stay up way past my bedtime, typing in BASIC programs and hoping they’d work.</p>
<h2 id="is-it-worth-it">Is It Worth It?</h2>
<p>At $299.99 (for the basic beige model, what I have), it’s not cheap. But then again, neither was the original C64 when it came out. And just like back then, this isn’t just a computer, it’s an experience.</p>
<p>If you grew up with the C64, you’ll understand. If you didn’t, well… this might just be the best way to find out what you missed.</p>
<p>I don’t know if I’ll use it every day. But I know I’ll keep it on my desk, within reach, for those moments when I need a little nostalgia or when I just want to remember what it was like to &lsquo;boot&rsquo; up a computer and feel like anything was possible.</p>
<p>Now, if you’ll excuse me, I have a SID chip to abuse and some old type-in listings to revisit.</p>
<p>The Commodore 64 Ultimate isn’t just a retro computer, it’s a time machine with modern conveniences. It’s a love letter to the era of 1 MHz processors and 64 KB of RAM, wrapped in a package that makes it accessible and enjoyable in 2025. For anyone who ever loved the C64, this is as close as it gets to coming home.</p>
<p>Thank you <a href="https://www.perifractic.com" target="_blank" rel="noopener noreffer ">Christian Simpson aka Peri Fractic</a> to make this possible again!</p>
<p><strong>RUN</strong></p>
]]></description></item><item><title>I Built an MCP Server (Almost) Without Writing Code</title><link>https://clemens.ms/i-built-an-mcp-server-almost-without-writing-code/</link><pubDate>Mon, 11 Aug 2025 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/i-built-an-mcp-server-almost-without-writing-code/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><p>I’ve been watching <strong>Model Context Protocol</strong> (MCP) servers pop up everywhere as the glue between AI agents and the real world. The pitch is simple: expose tools and data through a standard protocol and suddenly your AI agents can plan trips, analyze documents, query databases, or in my case, work with maps. MCP clicked for me because it’s opinionated where it matters and unopinionated where it shouldn’t. It standardizes how clients and servers talk, but it doesn’t box you into a single stack. Think of it as the USB-C of AI integrations: one cable, many devices.  ￼ ￼</p>
<p>I’m a developer who lives in <strong>Visual Studio Code</strong> and ships on Azure. I wanted to see how far I could get by letting AI do the hands-on coding while I focused on architecture, constraints, and review. The goal was audacious but grounded: build a practical Azure Maps MCP server that exposes real geospatial capabilities as MCP tools, and get there largely by prompting. The result is live in a public repo if you want to skim or run it: <a href="https://github.com/cschotte/azure-maps-mcp" target="_blank" rel="noopener noreffer ">cschotte/azure-maps-mcp</a>.</p>
<p>What follows is the story of how I used <strong>GitHub Copilot</strong> in VS Code to go from a blank folder to a working MCP server backed by Azure Functions and the Azure Maps SDKs and REST APIs. I’ll walk through the prompts, the refactors, the architectural guardrails I put in, and the moments where the agent went off the rails and I had to step in.</p>
<h2 id="why-mcp--azure-maps">Why MCP + Azure Maps?</h2>
<p>Maps make agents useful. Route planning, isochrones, reverse geocoding, POI search, static renders, time zones, weather, even IP-to-country, these are building blocks for real workflows. Azure Maps gives you all of that via .NET SDKs and REST. I wanted a server that exposes a curated, batteries-included set of tools that an LLM can call safely and consistently.  ￼</p>
<p>MCP gives me the language to describe those tools and a predictable way to call them from agentic clients. On the server side, I chose Azure Functions (isolated worker) so I could target modern .NET independently of the Functions host, wire up DI cleanly, and run everything locally with the Functions runtime while I iterate in VS Code.  ￼</p>
<h2 id="the-no-code-setup-that-still-needs-engineering-judgment">The “No-Code” Setup That Still Needs Engineering Judgment</h2>
<p>I started with an empty folder in VS Code and asked Copilot Chat to scaffold an Azure Functions isolated worker project in C# targeting .NET 9. I was explicit about constraints: no in-process model, use DI, and keep the entrypoint minimal. Copilot generated <code>Program.cs</code>, a project with a Functions host, and a skeleton for configuration. I added a Services layer for Azure Maps, a Tools layer for the MCP endpoints, and a Common area for input validation, a small REST helper, and a uniform response envelope.</p>
<p>Copilot wrote the first pass of the <code>Program.cs</code>. I nudged it toward what I consider a healthy baseline: explicit logging, named <code>HttpClient</code> for REST calls, and a single place to bind the Azure Maps key from configuration (local dev reads from <code>local.settings.json</code>; production expects environment or Key Vault). The entrypoint ended up looking like this:</p>
<p>I asked Copilot to generate VS Code tasks so that <strong>F5</strong> would build and start the Functions host and attach the debugger. It created a build (functions) task and a <code>func: host start</code> task wired to the Azure Functions Core Tools. From there, local development was a single keypress. The repo’s <a href="https://github.com/cschotte/azure-maps-mcp" target="_blank" rel="noopener noreffer ">README</a> shows the same flow if you want to reproduce it.  ￼</p>
<h2 id="modeling-the-tools-the-way-agents-actually-use-them">Modeling the Tools the Way Agents Actually Use Them</h2>
<p>I didn’t try to mirror every Azure Maps API. Instead, I curated a set that maps to common agent intents: find a place, reverse geocode and fetch boundaries, plan a route or compute an isochrone, fetch nearby POIs, render a static map, resolve an IP to a country, get a time zone, pull weather, and snap GPS points to roads. The server exposes these as discrete MCP tools with small, stable input shapes and predictable outputs.</p>
<p>Two early patterns mattered:</p>
<ol>
<li>A uniform response envelope. Every tool returns <code>{ success, meta, data }</code>. Lists use <code>{ query, items, summary }</code>; singletons use <code>{ query, result }</code>. This made it much easier to write prompts on the client side like “call location_find and show me the first item’s coordinates; if success is false, summarize the error.”</li>
<li>Stringy booleans at the boundary. Some MCP clients bind function args as strings. I kept boolean-like inputs as &ldquo;true&rdquo;/&ldquo;false&rdquo; at the tool boundary, then normalized them to real booleans in the handlers. It’s a small accommodation that avoided brittle binding errors across clients.</li>
</ol>
<p>For example, the <strong>location_find</strong> tool accepts a text query and an <strong>includeBoundaries</strong> flag. Under the hood it calls Azure Maps Search and, if requested, walks a fallback strategy for polygons (locality → postalCode → adminDistrict → countryRegion) because boundary availability varies by place and level. Azure Maps’ coverage nuances are real; your tool needs to be defensive.  ￼</p>
<h2 id="where-copilot-shined">Where Copilot Shined</h2>
<p>I used three Copilot experiences constantly:</p>
<p><strong>Ask mode</strong> was my rubber duck and librarian. I’d drop a doc link to Azure Maps Time Zone or Weather and say “extract the minimal request/response we need and sketch a C# wrapper.” It would lift the right REST path, query parameters, and payload into a clean method. For SDK-backed APIs like Search or Routing, I asked it to show the equivalent with the .NET client. That cut my “find the right API and object model” time to minutes.  ￼</p>
<p><strong>Edit mode</strong> let me refactor wide. When I noticed duplication in validation and HTTP error handling, I asked Copilot to “extract and centralize validation rules for lat/lon, radius, and ISO codes; make all tools use the same guard methods; return 400 with a consistent shape when validation fails.” It proposed diffs across multiple files, and I accepted them chunk by chunk.  ￼</p>
<p><strong>Agent mode</strong> was my power tool for multi-step tasks, like turning three loosely similar tool handlers into a single <strong>navigation_calculate</strong> that handles directions, matrix, and range (isochrone) flows with a <code>calculationType</code> discriminator. It iterated on compiler errors and mismatched types until it ran. I still reviewed the final diff like a hawk, but this mode saved hours.  ￼</p>
<p>Copilot inevitably over-codes if you let it. It suggested extra abstractions I didn’t need and occasionally “fixed” something another model wrote by undoing it. I kept it on task with narrow prompts, frequent test runs, and a willingness to say “back up two commits and try a smaller step.” The more precise and isolated the task, the better the result.</p>
<h2 id="clean-inputs-predictable-outputs">Clean Inputs, Predictable Outputs</h2>
<p>Each Function exposes one MCP tool via a simple HTTP trigger. Instead of binding directly to arbitrary JSON, I had Copilot generate specific request models with explicit types and validation attributes, then a controller-style handler that calls the service and wraps the result in the uniform envelope.</p>
<p>One subtle but important lesson: when you design for LLMs, error shapes matter. I made Copilot implement a small error type with a code, a friendly message, and a details bag. It turned cryptic HTTP exceptions into actionable feedback an agent can reason about.</p>
<h2 id="copilot-for-the-glue-work">Copilot for the “Glue Work”</h2>
<p>The time savings weren’t just in code. I used AI smart actions in VS Code to generate commit messages that accurately summarized multi-file changes, then tweaked intent in a sentence or two. For README work, I pasted code and asked Copilot to explain usage tersely and to propose a clear “Build and Run” section. It also generated the local tasks.json pipeline that lets me run the Functions host with one command. These things used to be procrastination traps; now they’re minutes.  ￼</p>
<h2 id="what-went-sideways">What Went Sideways</h2>
<p>Letting the agent roam is seductive. I tried one broad prompt “consolidate the three navigation handlers into a single tool with a discriminator; add docs and tests” and watched complexity creep in. The model introduced extra layers and re-named DTOs mid-diff. I aborted, rewrote the prompt into three atomic tasks, and the result was clean.</p>
<p>Model hopping also caused “helpful undo” moments. One model preferred slightly different DTO names and dutifully refactored usages, breaking earlier code. My rule now: pick one model per task, keep the diff small, and run the host between steps. Copilot is powerful, but you remain the engineer of record.</p>
<h2 id="lessons-im-taking-forward">Lessons I’m Taking Forward</h2>
<p>Agentic tooling is finally good enough to feel like a teammate. The biggest unlock wasn’t that Copilot wrote code; it was that it kept context across the repo, docs, diffs, and terminal output, and it could operate in different modes tailored to what I needed, <strong>ask</strong>, <strong>edit</strong>, or <strong>act</strong>. The trick is to design like a senior engineer and prompt like one: define constraints, choose simple interfaces, keep tasks small, and insist on clear error stories. Copilot will happily pour concrete, but you still have to draw the blueprint.  ￼</p>
<p>On the product side, MCP is a pragmatic standard that lets me expose Azure Maps safely to any MCP-aware client without yet another bespoke integration. It’s early days, but the “USB-C for AI apps” metaphor is earning its keep.  ￼</p>
]]></description></item><item><title>Commodore</title><link>https://clemens.ms/commodore/</link><pubDate>Tue, 05 Aug 2025 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/commodore/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><p>A personal history in a few very nerdy chapters</p>
<h2 id="pac-man">Pac-Man</h2>
<p>The first “computer” that really knocked on my brain wasn’t even called a computer. It was an <a href="https://en.wikipedia.org/wiki/Atari_2600" target="_blank" rel="noopener noreffer ">Atari 2600</a> with those giant wood-paneled vibes, a plastic spaceship parked under a living-room TV. Somewhere far from home (friends of my parents), the kind of visit where adults drink coffee forever, I met Pac-Man and the notorious E.T. They weren’t just games, they were a portal. The graphics were blocky miracles, the sound was pure electricity, and my head did that little swivel where a new obsession clicks into place. I didn’t own one. I barely got to touch it. But the idea got in. That was enough.</p>
<h2 id="the-commodore-64">The Commodore 64</h2>
<p>Back then a computer at home was a rare beast, the sort of thing you circled in a catalog and dreamed about. My dad heard coworkers talking about this “Commodore” machine and, after saving for ages, brought home a <strong>Commodore 64C</strong> with a tape drive, a color TV to borrow as a monitor, and one noble joystick that took more abuse than a drum kit. The original ad my dad marked up made it clear, this was an investment. For him it was “education.” For me and my brother, it was warp drive.</p>
<p>We fired it up and there it was, the blue screen that launched a million obsessions: <strong>READY.</strong> No OS like we think of today. Just the ROM, the kernel, and Commodore BASIC blinking at you like a dare. Our first flights were games, like <a href="https://www.lemon64.com/game/chopper-hunt" target="_blank" rel="noopener noreffer ">Chopper Hunt</a> and the brutal <a href="https://www.lemon64.com/game/fort-apocalypse" target="_blank" rel="noopener noreffer ">Fort Apocalypse</a>, which taught me two things: one, helicopters are hard; two, loading from tape requires saintly patience.</p>
<h2 id="basic">BASIC</h2>
<p>The C64 came with that ring bound manual that didn’t just tell you which key was <strong>RUN/STOP</strong> but taught you how to make the machine sing. I devoured First Computer Library titles like “Computer Fun” and “Simple BASIC” by Gaby Waters, beautifully illustrated by Graham Round. <a href="https://en.wikipedia.org/wiki/Commodore_BASIC" target="_blank" rel="noopener noreffer ">BASIC</a> was my first superpower. I typed, I ran, I crashed, I learned. Printing scrolling text felt like broadcasting to the world. Making a sprite walk across the screen felt like building life from numbers.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-Basic">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Basic" data-lang="Basic"><span class="line"><span class="cl"><span class="nl">10</span><span class="w"> </span><span class="kr">PRINT</span><span class="w"> </span><span class="s2">&#34;Hello World!&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nl">20</span><span class="w"> </span><span class="kr">GOTO</span><span class="w"> </span><span class="nl">10</span></span></span></code></pre></div></div>
<p>At some point the tape drive had to go. Floppies arrived like the future showing up early. Faster loads, easier saves, fewer rituals. Then came the add-ons that turned the C64 from a friendly pet into a pocket monster, the <a href="https://retro.ramonddevrede.nl/kcs-power-cartridge/" target="_blank" rel="noopener noreffer ">KCS Power Cartridge</a>, followed by <a href="https://en.wikipedia.org/wiki/The_Final_Cartridge_III" target="_blank" rel="noopener noreffer ">The Final Cartridge III</a> with its delicious machine-code monitor. Suddenly I could freeze programs, peek inside, dump sprites, edit text right there in memory. It felt like lifting the hood and discovering the engine actually wanted to talk.</p>
<h2 id="assembly">Assembly</h2>
<p>I joined Computer Club Zeeland (CCZ), where Saturdays smelled like solder and success. We swapped disks, traded tips, and most importantly, shared knowledge. I kept a handwritten list of every relevant PEEK and POKE, plus addresses for C64 and SID registers like they were phone numbers of superheroes. BASIC had been great, but I wanted speed and control, so I dove into assembly for the <a href="https://en.wikipedia.org/wiki/MOS_Technology_6510" target="_blank" rel="noopener noreffer ">MOS 6510</a>, that quirky cousin of the legendary 6502. When your world is 64 KB wide and your CPU ticks around 1 MHz, you learn to think like a tightrope walker. Every byte counts, every cycle matters, and cleverness beats brute force nine times out of ten.</p>
<p>It still blows my mind when I look at today’s software, how much stuff we sling around just to draw a button. Back then, a demo would swirl a &rsquo;thousand&rsquo; sprites and play a melody that could break your heart, all while fitting into memory you could scribble on a napkin.</p>
<h2 id="the-amiga-500">The Amiga 500</h2>
<p>At CCZ meetups I kept drifting toward a table that sounded different. The Commodore Amiga had color that didn’t just glow,  it danced. It had audio that wasn’t beeps, it was music. And it had a proper desktop, Workbench, that made the PC’s DOS prompt look like a gas station receipt. I did jobs, saved coins, begged grandparents, and eventually landed an Amiga 500. The built-in disk drive sounded like a tiny factory making joy.</p>
<p>I fell headfirst into <a href="https://en.wikipedia.org/wiki/Deluxe_Paint" target="_blank" rel="noopener noreffer ">DeluxePaint</a> and lost countless evenings drawing pixels until they started to feel like atoms. Animations, palettes, and the delicious thunk of saving to disk, my creative loop clicked. And yes, I kept programming, this time with <a href="https://en.wikipedia.org/wiki/HiSoft_Systems" target="_blank" rel="noopener noreffer ">Devpac 3 Assembler</a> and the Amiga ROM Kernel Reference Manuals. Nothing makes you feel more unstoppable than compiler warnings you finally understand.</p>
<h2 id="the-amiga-1200">The Amiga 1200</h2>
<p>Eventually I sold the Amiga 500 and upgraded to an Amiga 1200, which felt like getting glasses and realizing trees have leaves. Better graphics, more memory, and then the absolute splurge, a 120 MB hard drive that cost me 800 Dutch guilders. I added a board with a MC68030 and an FPU, which my 3D software (LightWave 3D) gobbled up like candy. Through the first couple years of my Computer Information Sciences and Technology studies, I was still hardcore Amiga. PCs with MS-DOS or even Windows 3.1 felt like a downgrade in soul. The Amiga wasn’t just a machine, it was a party trick that never got old.</p>
<p>And then the music stopped. Commodore went bankrupt. The line that had taught me how to think about computers, that had taught me how to build, draw, and dream, just… ended. I kept the A1200 and I still have it, now with a few modern bionics: extra hardware, memory, fresh connectors, even HDMI. Every once in a while I power it up and the room gets that old glow again.</p>
<h2 id="what-the-8-bit-computers-taught-me">What the 8-Bit computers taught me</h2>
<p>Those early machines taught me a kind of respect. When you only have 64 KB to play with, you learn restraint. When your CPU runs near 1 MHz, you learn patience and precision. And when you’re forced to understand memory maps, interrupts, and why your raster bar flickers, you learn to own your code. Today’s devices are miracles, but it’s easy to forget what sits under the glass. You don’t have to know how a car’s engine works to drive, but if you’re a developer, understanding how the pistons move will make you smoother on every curve.</p>
<h2 id="commodore-is-back">Commodore is back</h2>
<p>Here’s the plot twist I didn’t see coming, <a href="https://www.commodore.net" target="_blank" rel="noopener noreffer "><strong>Commodore is back</strong></a> under Christian “Perifractic” Simpson (yep, the one from <a href="https://www.youtube.com/@RetroRecipes" target="_blank" rel="noopener noreffer ">Retro Recipes</a>). The first new product is the <strong>Commodore 64 Ultimate</strong>, a modern C64 that isn’t just emulation but the real silicon soul reborn. Of course I bought one. Of course I did. Sometime this year, my first true computer will walk back through my door wearing a new jacket, and I’m not even pretending to be chill about it.</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/N8r4LRcOXc?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<h2 id="ready">READY.</h2>
<p>From an Atari joystick miles from home to a blue screen that taught me the word <strong>READY.</strong>, from PEEK and POKE to copper lists and DPaint brushes, the journey has always been the same, curiosity, tinkering, and the sheer thrill of making a machine do something it didn’t do a minute ago. My first computers didn’t just run programs, they rewired my brain. And as I wait for that C64 Ultimate to arrive, I can almost hear the SID warming up. One more program to write. One more sprite to nudge. One more chapter to load.</p>
<p><strong>RUN</strong></p>
]]></description></item><item><title>Building a personal AI chat assistant with semantic search</title><link>https://clemens.ms/building-a-personal-ai-chat-assistant-with-semantic-search/</link><pubDate>Mon, 04 Aug 2025 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/building-a-personal-ai-chat-assistant-with-semantic-search/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><h2 id="why-i-built-an-ai-assistant-for-my-blog">Why I Built an AI Assistant for My Blog</h2>
<p>I wanted my blog to do more than list posts. I wanted visitors to be able to ask natural questions about me, my work, and anything I’ve written, and get answers that cite the right articles without me hand. In my <a href="/running-n8n-on-azure-to-power-a-ai-chat-agent/" rel="">previous post</a> I laid the infrastructure groundwork by running n8n on Azure as an orchestration layer. This article goes deeper into how I assembled the chat assistant itself and wired it to semantic search so it actually “<strong>knows</strong>” my content rather than doing a brittle keyword lookup.</p>
<h2 id="structuring-my-blog-data-for-search-and-ai">Structuring My Blog Data for Search and AI</h2>
<p>I started by shaping the data. My site is a static <a href="https://gohugo.io" target="_blank" rel="noopener noreffer ">Hugo</a> build, which is convenient because Hugo can emit not just HTML and RSS but also JSON. That gave me a clean content feed I could parse from n8n and index into <a href="https://learn.microsoft.com/en-us/azure/search/search-what-is-azure-search" target="_blank" rel="noopener noreffer ">Azure AI Search</a>. In <code>hugo.toml</code> I enabled JSON for the homepage and created a minimal contract for the assistant to consume. The output is a single <code>index.json</code> containing one object per page with normalized, plain-text content and the metadata I care about. The important detail is that the <strong>id</strong> and a stable <strong>id_hash</strong> are derived from the relative permalink, keeping updates idempotent when I re-publish.</p>
<p>This is the JSON template I use under <code>/layouts/index.json</code>:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-json">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span><span class="err">{/*</span> <span class="err">Homepage</span> <span class="err">JSON</span> <span class="err">output</span> <span class="err">for</span> <span class="err">Azure</span> <span class="err">AI</span> <span class="err">Search</span> <span class="err">vector</span> <span class="err">store</span> <span class="err">*/</span><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="p">{</span><span class="err">{-</span> <span class="err">$pages</span> <span class="err">:=</span> <span class="err">where</span> <span class="err">.Site.RegularPages</span> <span class="nt">&#34;Draft&#34;</span> <span class="s2">&#34;!=&#34;</span> <span class="kc">true</span> <span class="err">-</span><span class="p">}</span><span class="err">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="err">{-</span> <span class="err">range</span> <span class="err">$index,</span> <span class="err">$page</span> <span class="err">:=</span> <span class="err">$pages</span> <span class="err">-</span><span class="p">}</span><span class="err">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span><span class="err">{-</span> <span class="err">if</span> <span class="err">$index</span> <span class="err">-</span><span class="p">}</span><span class="err">}</span><span class="p">,{</span><span class="err">{-</span> <span class="err">end</span> <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;id&#34;</span><span class="p">:</span> <span class="p">{</span><span class="err">{</span> <span class="err">$page.RelPermalink</span> <span class="err">|</span> <span class="err">jsonify</span> <span class="p">}},</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;id_hash&#34;</span><span class="err">:</span> <span class="p">{</span><span class="err">{</span> <span class="err">$page.RelPermalink</span> <span class="err">|</span> <span class="err">md5</span> <span class="err">|</span> <span class="err">jsonify</span> <span class="p">}</span><span class="err">}</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;title&#34;</span><span class="err">:</span> <span class="p">{</span><span class="err">{</span> <span class="err">$page.Title</span> <span class="err">|</span> <span class="err">jsonify</span> <span class="p">}</span><span class="err">}</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;description&#34;</span><span class="err">:</span> <span class="p">{</span><span class="err">{</span> <span class="err">$page.Description</span> <span class="err">|</span> <span class="err">default</span> <span class="err">$page.Summary</span> <span class="err">|</span> <span class="err">jsonify</span> <span class="p">}</span><span class="err">}</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;content&#34;</span><span class="err">:</span> <span class="p">{</span><span class="err">{</span> <span class="err">$page.Content</span> <span class="err">|</span> <span class="err">plainify</span> <span class="err">|</span> <span class="err">jsonify</span> <span class="p">}</span><span class="err">}</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;url&#34;</span><span class="err">:</span> <span class="p">{</span><span class="err">{</span> <span class="err">$page.Permalink</span> <span class="err">|</span> <span class="err">jsonify</span> <span class="p">}</span><span class="err">}</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;relative_url&#34;</span><span class="err">:</span> <span class="p">{</span><span class="err">{</span> <span class="err">$page.RelPermalink</span> <span class="err">|</span> <span class="err">jsonify</span> <span class="p">}</span><span class="err">}</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;date&#34;</span><span class="err">:</span> <span class="p">{</span><span class="err">{</span> <span class="err">$page.Date.Format</span> <span class="nt">&#34;2006-01-02T15:04:05Z07:00&#34;</span> <span class="err">|</span> <span class="err">jsonify</span> <span class="p">}</span><span class="err">}</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;publishDate&#34;</span><span class="err">:</span> <span class="p">{</span><span class="err">{</span> <span class="err">$page.PublishDate.Format</span> <span class="nt">&#34;2006-01-02T15:04:05Z07:00&#34;</span> <span class="err">|</span> <span class="err">jsonify</span> <span class="p">}</span><span class="err">}</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;lastmod&#34;</span><span class="err">:</span> <span class="p">{</span><span class="err">{</span> <span class="err">$page.Lastmod.Format</span> <span class="nt">&#34;2006-01-02T15:04:05Z07:00&#34;</span> <span class="err">|</span> <span class="err">jsonify</span> <span class="p">}</span><span class="err">}</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;categories&#34;</span><span class="err">:</span> <span class="p">{</span><span class="err">{</span> <span class="err">$page.Params.categories</span> <span class="err">|</span> <span class="err">default</span> <span class="err">slice</span> <span class="err">|</span> <span class="err">jsonify</span> <span class="p">}</span><span class="err">}</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;tags&#34;</span><span class="err">:</span> <span class="p">{</span><span class="err">{</span> <span class="err">$page.Params.tags</span> <span class="err">|</span> <span class="err">default</span> <span class="err">slice</span> <span class="err">|</span> <span class="err">jsonify</span> <span class="p">}</span><span class="err">}</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;featured&#34;</span><span class="err">:</span> <span class="p">{</span><span class="err">{</span> <span class="err">$page.Params.featured</span> <span class="err">|</span> <span class="err">default</span> <span class="err">false</span> <span class="p">}</span><span class="err">}</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;draft&#34;</span><span class="err">:</span> <span class="p">{</span><span class="err">{</span> <span class="err">$page.Draft</span> <span class="p">}</span><span class="err">}</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;type&#34;</span><span class="err">:</span> <span class="p">{</span><span class="err">{</span> <span class="err">$page.Type</span> <span class="err">|</span> <span class="err">jsonify</span> <span class="p">}</span><span class="err">}</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;section&#34;</span><span class="err">:</span> <span class="p">{</span><span class="err">{</span> <span class="err">$page.Section</span> <span class="err">|</span> <span class="err">jsonify</span> <span class="p">}</span><span class="err">}</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;wordCount&#34;</span><span class="err">:</span> <span class="p">{</span><span class="err">{</span> <span class="err">$page.WordCount</span> <span class="p">}</span><span class="err">}</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;readingTime&#34;</span><span class="err">:</span> <span class="p">{</span><span class="err">{</span> <span class="err">$page.ReadingTime</span> <span class="p">}</span><span class="err">}</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;keywords&#34;</span><span class="err">:</span> <span class="p">{</span><span class="err">{</span> <span class="err">delimit</span> <span class="err">(union</span> <span class="err">($page.Params.tags</span> <span class="err">|</span> <span class="err">default</span> <span class="err">slice)</span> <span class="err">($page.Params.categories</span> <span class="err">|</span> <span class="err">default</span> <span class="err">slice))</span> <span class="nt">&#34;,&#34;</span> <span class="err">|</span> <span class="err">jsonify</span> <span class="p">}</span><span class="err">}</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;searchable_content&#34;</span><span class="err">:</span> <span class="p">{</span><span class="err">{</span> <span class="err">printf</span> <span class="nt">&#34;%s %s %s %s&#34;</span> <span class="err">$page.Title</span> <span class="err">$page.Description</span> <span class="err">($page.Content</span> <span class="err">|</span> <span class="err">plainify)</span> <span class="err">(delimit</span> <span class="err">($page.Params.tags</span> <span class="err">|</span> <span class="err">default</span> <span class="err">slice)</span> <span class="s2">&#34; &#34;</span><span class="err">)</span> <span class="err">|</span> <span class="err">jsonify</span> <span class="p">}</span><span class="err">}</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;summary&#34;</span><span class="err">:</span> <span class="p">{</span><span class="err">{</span> <span class="err">$page.Summary</span> <span class="err">|</span> <span class="err">plainify</span> <span class="err">|</span> <span class="err">jsonify</span> <span class="p">}</span><span class="err">}</span>
</span></span><span class="line"><span class="cl">      <span class="err">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="err">{-</span> <span class="err">end</span> <span class="p">}</span><span class="err">}</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span></span></span></code></pre></div></div>
<p>The <em>content</em> value is plain-text and already stripped of markup; this matters for embeddings because you want consistent tokenization, not HTML noise. I also assemble a <em>searchable_content</em> field that concatenates headline signals with the body and tags. While the assistant relies on <strong>vectors</strong>, Azure’s semantic search benefits from lexical hints for re-ranking and captions, so giving it both worlds improves quality.</p>
<h2 id="indexing-with-azure-ai-search-and-openai-embeddings">Indexing with Azure AI Search and OpenAI Embeddings</h2>
<p>With content ready, I defined an Azure AI Search index. The index has text fields for <strong>title</strong>, <strong>content</strong>, <strong>URL</strong> and <strong>keywords</strong>, plus a <strong>vector</strong> field that stores the embedding for each page. I use <strong>cosine similarity</strong> and <strong>HNSW</strong> for approximate nearest neighbor retrieval. The embedding model is OpenAI’s <code>text-embedding-3-small</code> hosted on <a href="https://ai.azure.com/" target="_blank" rel="noopener noreffer ">Azure AI Foundry</a>; it returns a <strong>1536-dimension vector</strong> which must match the index’s dimensions property. If these don’t match, Azure will reject documents at ingestion time, so it’s worth calling that out explicitly.</p>
<p>Here is the index definition I deployed:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-json">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><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;@odata.etag&#34;</span><span class="p">:</span> <span class="s2">&#34;\&#34;0x8DDD2B748AD9259\&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;clemens&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;fields&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;id&#34;</span><span class="p">,</span> <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;Edm.String&#34;</span><span class="p">,</span> <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>  <span class="nt">&#34;searchable&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="nt">&#34;retrievable&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nt">&#34;stored&#34;</span><span class="p">:</span> <span class="kc">true</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;title&#34;</span><span class="p">,</span> <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;Edm.String&#34;</span><span class="p">,</span> <span class="nt">&#34;searchable&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nt">&#34;retrievable&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nt">&#34;stored&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nt">&#34;analyzer&#34;</span><span class="p">:</span> <span class="s2">&#34;standard.lucene&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;url&#34;</span><span class="p">,</span> <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;Edm.String&#34;</span><span class="p">,</span> <span class="nt">&#34;searchable&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nt">&#34;retrievable&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nt">&#34;stored&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nt">&#34;analyzer&#34;</span><span class="p">:</span> <span class="s2">&#34;standard.lucene&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;content&#34;</span><span class="p">,</span> <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;Edm.String&#34;</span><span class="p">,</span> <span class="nt">&#34;searchable&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nt">&#34;retrievable&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nt">&#34;stored&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nt">&#34;analyzer&#34;</span><span class="p">:</span> <span class="s2">&#34;standard.lucene&#34;</span> <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="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;vector&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;Collection(Edm.Single)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;searchable&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;retrievable&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;stored&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;dimensions&#34;</span><span class="p">:</span> <span class="mi">1536</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;vectorSearchProfile&#34;</span><span class="p">:</span> <span class="s2">&#34;vector-profile-1754242634632&#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 class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;keywords&#34;</span><span class="p">,</span> <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;Edm.String&#34;</span><span class="p">,</span> <span class="nt">&#34;searchable&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nt">&#34;retrievable&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nt">&#34;stored&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nt">&#34;analyzer&#34;</span><span class="p">:</span> <span class="s2">&#34;standard.lucene&#34;</span> <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="nt">&#34;similarity&#34;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&#34;@odata.type&#34;</span><span class="p">:</span> <span class="s2">&#34;#Microsoft.Azure.Search.BM25Similarity&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;semantic&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;configurations&#34;</span><span class="p">:</span> <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="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;clemens&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;rankingOrder&#34;</span><span class="p">:</span> <span class="s2">&#34;BoostedRerankerScore&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;prioritizedFields&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nt">&#34;titleField&#34;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&#34;fieldName&#34;</span><span class="p">:</span> <span class="s2">&#34;title&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">          <span class="nt">&#34;prioritizedContentFields&#34;</span><span class="p">:</span> <span class="p">[{</span> <span class="nt">&#34;fieldName&#34;</span><span class="p">:</span> <span class="s2">&#34;content&#34;</span> <span class="p">}],</span>
</span></span><span class="line"><span class="cl">          <span class="nt">&#34;prioritizedKeywordsFields&#34;</span><span class="p">:</span> <span class="p">[{</span> <span class="nt">&#34;fieldName&#34;</span><span class="p">:</span> <span class="s2">&#34;keywords&#34;</span> <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></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;vectorSearch&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;algorithms&#34;</span><span class="p">:</span> <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="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;vector-config-1754242641035&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;kind&#34;</span><span class="p">:</span> <span class="s2">&#34;hnsw&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;hnswParameters&#34;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&#34;metric&#34;</span><span class="p">:</span> <span class="s2">&#34;cosine&#34;</span><span class="p">,</span> <span class="nt">&#34;m&#34;</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span> <span class="nt">&#34;efConstruction&#34;</span><span class="p">:</span> <span class="mi">400</span><span class="p">,</span> <span class="nt">&#34;efSearch&#34;</span><span class="p">:</span> <span class="mi">500</span> <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="nt">&#34;profiles&#34;</span><span class="p">:</span> <span class="p">[{</span> <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;vector-profile-1754242634632&#34;</span><span class="p">,</span> <span class="nt">&#34;algorithm&#34;</span><span class="p">:</span> <span class="s2">&#34;vector-config-1754242641035&#34;</span> <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></code></pre></div></div>
<p>I kept the HNSW parameters conservative. A higher <strong>m</strong> increases the graph’s connectivity and memory footprint; dialing <em>efSearch</em> up lets me trade latency for recall at query time. Because my corpus is modest, I can afford an <em>efSearch</em> of 500 to make results stable without spiking response time.</p>
<h2 id="running-embedding-and-upload-workflows-in-n8n">Running Embedding and Upload Workflows in n8n</h2>
<p>For embeddings I deliberately chose <code>text-embedding-3-small</code> over the larger variant. The quality difference on my content was negligible while the latency and cost advantages were tangible. A personal site rarely needs cross-domain reasoning. What matters is that semantically related passages end up close in vector space. The model is trained for that, and it performs well for questions that don’t literally match my phrasing, for example, “Where did you study?” finds my education background even if the word “school” never appears in the text, because vector similarity captures that intent.</p>
<p>Ingestion runs through n8n. I created a workflow that fetches <code>https://clemens.ms/index.json</code>, iterates records, calls the Azure OpenAI embeddings endpoint for each page’s content, and then pushes the merged document to Azure AI Search. The payload I send looks like this, with n8n expressions injecting values from the previous steps:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-json">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><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;value&#34;</span><span class="p">:</span> <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="nt">&#34;@search.action&#34;</span><span class="p">:</span> <span class="s2">&#34;mergeOrUpload&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="p">{</span><span class="err">{</span> <span class="err">JSON.stringify($json.id)</span> <span class="p">}},</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;title&#34;</span><span class="err">:</span> <span class="p">{</span><span class="err">{</span> <span class="err">JSON.stringify($json.title)</span> <span class="p">}</span><span class="err">}</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;content&#34;</span><span class="err">:</span> <span class="p">{</span><span class="err">{</span> <span class="err">JSON.stringify($json.content)</span> <span class="p">}</span><span class="err">}</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;url&#34;</span><span class="err">:</span> <span class="p">{</span><span class="err">{</span> <span class="err">JSON.stringify($json.url)</span> <span class="p">}</span><span class="err">}</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;keywords&#34;</span><span class="err">:</span> <span class="p">{</span><span class="err">{</span> <span class="err">JSON.stringify($json.keywords)</span> <span class="p">}</span><span class="err">}</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;vector&#34;</span><span class="err">:</span> <span class="p">[</span> <span class="p">{</span><span class="err">{</span> <span class="err">$json.data[0].embedding</span> <span class="p">}</span><span class="err">}</span> <span class="p">]</span>
</span></span><span class="line"><span class="cl">    <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="p">}</span></span></span></code></pre></div></div>
<p>The choice of <strong>mergeOrUpload</strong> allows me to re-run the workflow after publishing without wiping the index. The <em>id</em> remains stable across deployments, so the document is updated in place. For now I index by page rather than chunking into paragraphs. With a small corpus this is fine and massively simplifies grounding. If I grow into long-form guides, I’ll switch to chunking with overlap to improve retrieval granularity and reduce token usage when I assemble the context.</p>
<h2 id="designing-the-chat-agent-with-tool-use-and-guardrails">Designing the Chat Agent with Tool-Use and Guardrails</h2>
<p>The assistant itself runs as an n8n “agent.” Conceptually it’s an orchestrator around a large language model with a single tool: semantic search against my index. I use <strong>GPT-5-mini</strong> for its reasoning and multi-turn memory. The agent has a compact <strong>system prompt</strong> that sets expectations, instructs the model to always consult the search tool when answering anything about my site, and reminds it to keep responses helpful and grounded. This is the skeleton of that prompt:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-text">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">You are ClemensGPT, a helpful, trustworthy, and knowledgeable AI assistant for the personal website and blog of Clemens Schotte (https://clemens.ms).
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Always use the Azure AI Search Index tool to search for answers about site content. Maintain conversational context across turns, but never fabricate facts; if the index has no answer, say so and suggest related posts.</span></span></code></pre></div></div>
<p>At query time I embed the user’s message using the same <code>text-embedding-3-small</code> model, which guarantees the vectors live in the same space as my documents. I submit a hybrid request that includes both the raw query and the vector, asking Azure to apply semantic configuration for re-ranking and captions. I request the top three results, which gives the model enough grounding without flooding its context window. The request looks like this:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-json">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><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;search&#34;</span><span class="p">:</span> <span class="s2">&#34;{{ $(&#39;Webhook&#39;).item.json.body.content }}&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;queryType&#34;</span><span class="p">:</span> <span class="s2">&#34;semantic&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;semanticConfiguration&#34;</span><span class="p">:</span> <span class="s2">&#34;clemens&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;vectorQueries&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="nt">&#34;vector&#34;</span><span class="p">:</span> <span class="p">[{</span><span class="err">{</span> <span class="err">$json.data[0].embedding</span> <span class="p">}</span><span class="err">}</span><span class="p">],</span> <span class="nt">&#34;fields&#34;</span><span class="p">:</span> <span class="s2">&#34;vector&#34;</span><span class="p">,</span> <span class="nt">&#34;k&#34;</span><span class="p">:</span> <span class="mi">30</span><span class="p">,</span> <span class="nt">&#34;kind&#34;</span><span class="p">:</span> <span class="s2">&#34;vector&#34;</span> <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="nt">&#34;select&#34;</span><span class="p">:</span> <span class="s2">&#34;id,title,content,keywords,url&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;top&#34;</span><span class="p">:</span> <span class="mi">3</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<h2 id="frontend-integration-and-final-deployment">Frontend Integration and Final Deployment</h2>
<p>The assistant then synthesizes a response by quoting or summarizing those passages. Because n8n gives me a built-in chat UI during development, I iterated on the prompt until the tool-use behavior was consistent. Once I was happy, I hardened <strong>CORS</strong> in the chat settings and embedded the production widget in my site with a simple script include:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-html">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">link</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;https://cdn.jsdelivr.net/npm/@n8n/chat/dist/style.css&#34;</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;stylesheet&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;module&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="kr">import</span> <span class="p">{</span> <span class="nx">createChat</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;https://cdn.jsdelivr.net/npm/@n8n/chat/dist/chat.bundle.es.js&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">createChat</span><span class="p">({</span> <span class="nx">webhookUrl</span><span class="o">:</span> <span class="s1">&#39;YOUR_PRODUCTION_WEBHOOK_URL&#39;</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span></span></span></code></pre></div></div>
<p>On the operational side I keep ingestion simple. A scheduled n8n trigger runs after I <strong>publish</strong>, pulls the fresh <code>index.json</code>, and updates the index. The Azure AI Search tier I’m on includes semantic capabilities; the free tier doesn’t.</p>
<p>The most satisfying test was asking questions the text doesn’t literally answer. “What technologies power this site?” brings back Hugo, Azure, and n8n from different posts and the about page. “What school did clemens study?” surfaces my background even though the word “school” isn’t used. That’s the point of using embeddings and semantic re-ranking together: intent over string matching, with just enough lexical signal to help the system pick the right snippets.</p>
<p>Visitors can now ask, and the site answers with context it actually understands. That feels like a small but meaningful upgrade to how a personal blog can work in 2025.</p>
]]></description></item><item><title>Podcast about AI agents and Azure Maps MCP server</title><link>https://clemens.ms/podcast-about-ai-agents-and-azure-maps-mcp-server/</link><pubDate>Sat, 02 Aug 2025 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/podcast-about-ai-agents-and-azure-maps-mcp-server/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><p>Last week I joined <a href="https://www.youtube.com/@geospatialfm" target="_blank" rel="noopener noreffer ">Geospatial FM</a>, the podcast hosted by <a href="https://www.linkedin.com/in/geospatialfm/" target="_blank" rel="noopener noreffer ">Wilfred Waters</a>, to talk about AI agents and the <a href="https://clemens.ms/enabling-geospatial-intelligence-in-llms-with-azure-maps-and-mcp/" target="_blank" rel="noopener noreffer ">Azure Maps MCP server</a> I had created and bloged about.</p>
<p>We touched on how Bing Maps is the familiar public-facing mapping service, while Azure Maps is the developer platform for bringing mapping, routing, traffic, and spatial analytics into enterprise and IoT apps. The heart of our conversation was about Model Context Protocol (MCP) and why it matters. MCP lets AI agents use tools and pull fresh data from APIs, so instead of guessing about roads, traffic, or places, an agent can call Azure Maps in real time.</p>
<h2 id="watch--listen-here">Watch &amp; listen here:</h2>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/Q8ZM6u1IB3U?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>Or you can also find it on YouTube, Spotify, and Substack:</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=Q8ZM6u1IB3U" target="_blank" rel="noopener noreffer ">YouTube</a></li>
<li><a href="https://open.spotify.com/episode/39KhXHSgZBTPgKezSmgRAE" target="_blank" rel="noopener noreffer ">Spotify</a></li>
<li><a href="https://www.geospatial.fm/p/azure-maps" target="_blank" rel="noopener noreffer ">Substack</a></li>
</ul>
]]></description></item><item><title>Running n8n on Azure to power a AI chat agent</title><link>https://clemens.ms/running-n8n-on-azure-to-power-a-ai-chat-agent/</link><pubDate>Wed, 30 Jul 2025 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/running-n8n-on-azure-to-power-a-ai-chat-agent/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><h2 id="a-lightweight-azure-backend-for-my-ai-agent">A lightweight Azure backend for my AI agent</h2>
<p>Over the past few weeks, I’ve been exploring different ways to power a personal AI agent for my blog, one that can answer questions about me, my background, and my work using context I provide. I wanted a simple, secure, and cost-effective backend that I fully control and can iterate on fast.</p>
<p><strong>n8n</strong> is a powerful open-source automation tool that’s perfect for wiring together APIs and logic without having to spin up tons of infrastructure.</p>
<p>But first, I needed to get n8n up and running on Azure.</p>
<p>This blog post documents exactly how I did it: deploying n8n on <strong>Azure Container Apps</strong>, connecting it securely to Azure Database for <strong>PostgreSQL</strong> Flexible Server, and setting everything up to follow best practices like private networking, basic auth, encryption, and persistent workflows.</p>
<h2 id="what-youll-need">What you’ll need</h2>
<p>Before I could start stitching together containers and databases, I needed to make sure my local environment was ready. For me, that meant working from my Windows machine using PowerShell (my go-to shell for anything Azure-related). I wanted this setup to be clean, repeatable, and fully self-contained, without depending on extra tooling or scripts from elsewhere.</p>
<p>The goal was to keep things simple but robust. With just a few PowerShell commands and the Azure CLI, I’d provision a complete backend in the cloud that felt lightweight yet production-grade.</p>
<p>At the core of this setup is a single Azure Container App running the official <code>n8nio/n8n</code> image. It’s backed by a PostgreSQL Flexible Server that lives securely inside a private VNet. Every connection is internal. Every service knows its place. Nothing is exposed unless I want it to be.</p>
<p>If you’re ready to follow along, all you really need is an updated Azure CLI, the Container Apps extension, and a PowerShell window. That’s where we begin:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-powershell">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="n">az</span> <span class="n">upgrade</span> <span class="p">-</span><span class="n">-yes</span>
</span></span><span class="line"><span class="cl"><span class="n">az</span> <span class="n">extension</span> <span class="n">add</span> <span class="p">-</span><span class="n">-name</span> <span class="n">containerapp</span> <span class="p">-</span><span class="n">-upgrade</span>
</span></span><span class="line"><span class="cl"><span class="n">az</span> <span class="n">login</span>
</span></span><span class="line"><span class="cl"><span class="n">az</span> <span class="n">account</span> <span class="nb">set </span><span class="p">-</span><span class="n">-subscription</span> <span class="s2">&#34;&lt;YOUR_SUBSCRIPTION_ID_OR_NAME&gt;&#34;</span></span></span></code></pre></div></div>
<h2 id="1-define-your-variables">1. Define your variables</h2>
<p>This code defines all the values we’ll use: region, resource names, credentials, and secrets. Random values are generated for passwords and encryption keys so you never have to hardcode them.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-powershell">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="c"># Region close to me (Netherlands)</span>
</span></span><span class="line"><span class="cl"><span class="nv">$LOCATION</span> <span class="p">=</span> <span class="s2">&#34;northeurope&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Resource names (lightweight but persistent)</span>
</span></span><span class="line"><span class="cl"><span class="nv">$RG</span>        <span class="p">=</span> <span class="s2">&#34;n8n&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$ENV_NAME</span>  <span class="p">=</span> <span class="s2">&#34;n8n-lite-env&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$APP_NAME</span>  <span class="p">=</span> <span class="s2">&#34;n8n-lite&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$VNET_NAME</span> <span class="p">=</span> <span class="s2">&#34;n8n-vnet&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$SUBNET_NAME</span> <span class="p">=</span> <span class="s2">&#34;n8n-subnet&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$POSTGRES_SUBNET_NAME</span> <span class="p">=</span> <span class="s2">&#34;postgres-subnet&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># PostgreSQL Flexible Server (required for n8n best practices)</span>
</span></span><span class="line"><span class="cl"><span class="nv">$POSTGRES_SERVER</span> <span class="p">=</span> <span class="s2">&#34;n8n-postgres-</span><span class="p">$(</span><span class="nb">Get-Random</span> <span class="n">-Maximum</span> <span class="mf">99999</span><span class="p">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$POSTGRES_DB</span> <span class="p">=</span> <span class="s2">&#34;n8n&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$POSTGRES_USER</span> <span class="p">=</span> <span class="s2">&#34;n8nadmin&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Basic auth for n8n editor</span>
</span></span><span class="line"><span class="cl"><span class="nv">$N8N_BASIC_USER</span> <span class="p">=</span> <span class="s2">&#34;admin&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Generate a strong random password for PostgreSQL and a 32-byte hex encryption key</span>
</span></span><span class="line"><span class="cl"><span class="nb">Add-Type</span> <span class="n">-AssemblyName</span> <span class="n">System</span><span class="p">.</span><span class="py">Security</span>
</span></span><span class="line"><span class="cl"><span class="nv">$bytes</span> <span class="p">=</span> <span class="nb">New-Object</span> <span class="n">byte</span><span class="p">[]</span> <span class="mf">32</span>
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="no">Security.Cryptography.RandomNumberGenerator</span><span class="p">]::</span><span class="n">Create</span><span class="p">().</span><span class="py">GetBytes</span><span class="p">(</span><span class="nv">$bytes</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">$N8N_ENCRYPTION_KEY</span> <span class="p">=</span> <span class="p">(</span><span class="nv">$bytes</span> <span class="p">|</span> <span class="nb">ForEach-Object</span> <span class="p">{</span> <span class="nv">$_</span><span class="p">.</span><span class="py">ToString</span><span class="p">(</span><span class="s2">&#34;x2&#34;</span><span class="p">)</span> <span class="p">})</span> <span class="n">-join</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$chars</span> <span class="p">=</span> <span class="p">(</span><span class="mf">48</span><span class="p">.</span><span class="mf">.57</span> <span class="p">+</span> <span class="mf">65</span><span class="p">.</span><span class="mf">.90</span> <span class="p">+</span> <span class="mf">97</span><span class="p">.</span><span class="mf">.122</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">$N8N_BASIC_PASSWORD</span> <span class="p">=</span> <span class="n">-join</span> <span class="p">(</span><span class="mf">1</span><span class="p">.</span><span class="mf">.24</span> <span class="p">|</span> <span class="nb">ForEach-Object</span> <span class="p">{</span> <span class="p">[</span><span class="no">char</span><span class="p">](</span><span class="nv">$chars</span> <span class="p">|</span> <span class="nb">Get-Random</span><span class="p">)</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="nv">$POSTGRES_PASSWORD</span> <span class="p">=</span> <span class="n">-join</span> <span class="p">(</span><span class="mf">1</span><span class="p">.</span><span class="mf">.24</span> <span class="p">|</span> <span class="nb">ForEach-Object</span> <span class="p">{</span> <span class="p">[</span><span class="no">char</span><span class="p">](</span><span class="nv">$chars</span> <span class="p">|</span> <span class="nb">Get-Random</span><span class="p">)</span> <span class="p">})</span></span></span></code></pre></div></div>
<h2 id="2-provision-secure-networking">2. Provision secure networking</h2>
<p>We’ll create a virtual network and two subnets: one for Container Apps and one for PostgreSQL. Each is delegated to the correct Azure service for secure, private communication.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-powershell">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="s2">&#34;</span><span class="se">`n</span><span class="s2">Creating resource group...&#34;</span> <span class="n">-ForegroundColor</span> <span class="n">Cyan</span>
</span></span><span class="line"><span class="cl"><span class="n">az</span> <span class="nb">group </span><span class="n">create</span> <span class="p">-</span><span class="n">-name</span> <span class="nv">$RG</span> <span class="p">-</span><span class="n">-location</span> <span class="nv">$LOCATION</span> <span class="p">|</span> <span class="nb">Out-Null</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Create VNet for secure communication (following best practices)</span>
</span></span><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="s2">&#34;Creating secure virtual network...&#34;</span> <span class="n">-ForegroundColor</span> <span class="n">Cyan</span>
</span></span><span class="line"><span class="cl"><span class="n">az</span> <span class="n">network</span> <span class="n">vnet</span> <span class="n">create</span> <span class="p">-</span><span class="n">-resource-group</span> <span class="nv">$RG</span> <span class="p">-</span><span class="n">-name</span> <span class="nv">$VNET_NAME</span> <span class="p">-</span><span class="n">-location</span> <span class="nv">$LOCATION</span> <span class="p">-</span><span class="n">-address-prefixes</span> <span class="mf">10.0</span><span class="p">.</span><span class="py">0</span><span class="p">.</span><span class="mf">0</span><span class="p">/</span><span class="mf">16</span> <span class="p">|</span> <span class="nb">Out-Null</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Create subnet for Container Apps</span>
</span></span><span class="line"><span class="cl"><span class="n">az</span> <span class="n">network</span> <span class="n">vnet</span> <span class="n">subnet</span> <span class="n">create</span> <span class="p">-</span><span class="n">-resource-group</span> <span class="nv">$RG</span> <span class="p">-</span><span class="n">-vnet-name</span> <span class="nv">$VNET_NAME</span> <span class="p">-</span><span class="n">-name</span> <span class="nv">$SUBNET_NAME</span> <span class="p">-</span><span class="n">-address-prefixes</span> <span class="mf">10.0</span><span class="p">.</span><span class="py">0</span><span class="p">.</span><span class="mf">0</span><span class="p">/</span><span class="mf">23</span> <span class="p">|</span> <span class="nb">Out-Null</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Create separate subnet for PostgreSQL</span>
</span></span><span class="line"><span class="cl"><span class="n">az</span> <span class="n">network</span> <span class="n">vnet</span> <span class="n">subnet</span> <span class="n">create</span> <span class="p">-</span><span class="n">-resource-group</span> <span class="nv">$RG</span> <span class="p">-</span><span class="n">-vnet-name</span> <span class="nv">$VNET_NAME</span> <span class="p">-</span><span class="n">-name</span> <span class="nv">$POSTGRES_SUBNET_NAME</span> <span class="p">-</span><span class="n">-address-prefixes</span> <span class="mf">10.0</span><span class="p">.</span><span class="py">2</span><span class="p">.</span><span class="mf">0</span><span class="p">/</span><span class="mf">24</span> <span class="p">|</span> <span class="nb">Out-Null</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Delegate Container Apps subnet</span>
</span></span><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="s2">&#34;Delegating subnet to Container Apps...&#34;</span> <span class="n">-ForegroundColor</span> <span class="n">Cyan</span>
</span></span><span class="line"><span class="cl"><span class="n">az</span> <span class="n">network</span> <span class="n">vnet</span> <span class="n">subnet</span> <span class="n">update</span> <span class="p">-</span><span class="n">-resource-group</span> <span class="nv">$RG</span> <span class="p">-</span><span class="n">-vnet-name</span> <span class="nv">$VNET_NAME</span> <span class="p">-</span><span class="n">-name</span> <span class="nv">$SUBNET_NAME</span> <span class="p">-</span><span class="n">-delegations</span> <span class="n">Microsoft</span><span class="p">.</span><span class="n">App</span><span class="p">/</span><span class="n">environments</span> <span class="p">|</span> <span class="nb">Out-Null</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Delegate PostgreSQL subnet</span>
</span></span><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="s2">&#34;Delegating subnet to PostgreSQL...&#34;</span> <span class="n">-ForegroundColor</span> <span class="n">Cyan</span>
</span></span><span class="line"><span class="cl"><span class="n">az</span> <span class="n">network</span> <span class="n">vnet</span> <span class="n">subnet</span> <span class="n">update</span> <span class="p">-</span><span class="n">-resource-group</span> <span class="nv">$RG</span> <span class="p">-</span><span class="n">-vnet-name</span> <span class="nv">$VNET_NAME</span> <span class="p">-</span><span class="n">-name</span> <span class="nv">$POSTGRES_SUBNET_NAME</span> <span class="p">-</span><span class="n">-delegations</span> <span class="n">Microsoft</span><span class="p">.</span><span class="n">DBforPostgreSQL</span><span class="p">/</span><span class="n">flexibleServers</span> <span class="p">|</span> <span class="nb">Out-Null</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$SUBNET_ID</span> <span class="p">=</span> <span class="n">az</span> <span class="n">network</span> <span class="n">vnet</span> <span class="n">subnet</span> <span class="n">show</span> <span class="p">-</span><span class="n">-resource-group</span> <span class="nv">$RG</span> <span class="p">-</span><span class="n">-vnet-name</span> <span class="nv">$VNET_NAME</span> <span class="p">-</span><span class="n">-name</span> <span class="nv">$SUBNET_NAME</span> <span class="p">-</span><span class="n">-query</span> <span class="n">id</span> <span class="n">-o</span> <span class="n">tsv</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="s2">&#34;✓ Secure network created with separate delegated subnets&#34;</span> <span class="n">-ForegroundColor</span> <span class="n">Green</span></span></span></code></pre></div></div>
<h2 id="3-create-a-secure-postgresql-server">3. Create a secure PostgreSQL server</h2>
<p>n8n officially recommends PostgreSQL for production. Here we deploy a private Flexible Server inside the delegated subnet. No public access, no internet exposure.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-powershell">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="s2">&#34;</span><span class="se">`n</span><span class="s2">Creating secure PostgreSQL Flexible Server...&#34;</span> <span class="n">-ForegroundColor</span> <span class="n">Cyan</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Create PostgreSQL with VNet integration (secure, no public access)</span>
</span></span><span class="line"><span class="cl"><span class="n">az</span> <span class="n">postgres</span> <span class="nb">flexible-server</span> <span class="n">create</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="p">-</span><span class="n">-resource-group</span> <span class="nv">$RG</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="p">-</span><span class="n">-name</span> <span class="nv">$POSTGRES_SERVER</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="p">-</span><span class="n">-location</span> <span class="nv">$LOCATION</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="p">-</span><span class="n">-admin-user</span> <span class="nv">$POSTGRES_USER</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="p">-</span><span class="n">-admin-password</span> <span class="nv">$POSTGRES_PASSWORD</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="p">-</span><span class="n">-sku-name</span> <span class="n">Standard_B1ms</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="p">-</span><span class="n">-tier</span> <span class="n">Burstable</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="p">-</span><span class="n">-storage-size</span> <span class="mf">32</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="p">-</span><span class="n">-version</span> <span class="mf">14</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="p">-</span><span class="n">-vnet</span> <span class="nv">$VNET_NAME</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="p">-</span><span class="n">-subnet</span> <span class="nv">$POSTGRES_SUBNET_NAME</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="p">-</span><span class="n">-yes</span> <span class="p">|</span> <span class="nb">Out-Null</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Create the n8n database</span>
</span></span><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="s2">&#34;Creating n8n database...&#34;</span> <span class="n">-ForegroundColor</span> <span class="n">Cyan</span>
</span></span><span class="line"><span class="cl"><span class="n">az</span> <span class="n">postgres</span> <span class="nb">flexible-server</span> <span class="n">db</span> <span class="n">create</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="p">-</span><span class="n">-resource-group</span> <span class="nv">$RG</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="p">-</span><span class="n">-server-name</span> <span class="nv">$POSTGRES_SERVER</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="p">-</span><span class="n">-database-name</span> <span class="nv">$POSTGRES_DB</span> <span class="p">|</span> <span class="nb">Out-Null</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="s2">&#34;✓ Secure PostgreSQL server created: </span><span class="nv">$POSTGRES_SERVER</span><span class="s2">&#34;</span> <span class="n">-ForegroundColor</span> <span class="n">Green</span></span></span></code></pre></div></div>
<h2 id="4-set-up-the-container-apps-environment">4. Set up the Container Apps environment</h2>
<p>Azure Container Apps supports full VNet integration, great for security and compliance. I disable Log Analytics to save costs and keep things simple.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-powershell">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="s2">&#34;</span><span class="se">`n</span><span class="s2">Creating secure Container Apps environment...&#34;</span> <span class="n">-ForegroundColor</span> <span class="n">Cyan</span>
</span></span><span class="line"><span class="cl"><span class="n">az</span> <span class="n">containerapp</span> <span class="n">env</span> <span class="n">create</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="p">-</span><span class="n">-resource-group</span> <span class="nv">$RG</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="p">-</span><span class="n">-name</span> <span class="nv">$ENV_NAME</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="p">-</span><span class="n">-location</span> <span class="nv">$LOCATION</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="p">-</span><span class="n">-infrastructure-subnet-resource-id</span> <span class="nv">$SUBNET_ID</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="p">-</span><span class="n">-logs-destination</span> <span class="n">none</span> <span class="p">|</span> <span class="nb">Out-Null</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="s2">&#34;✓ Secure Container Apps environment created&#34;</span> <span class="n">-ForegroundColor</span> <span class="n">Green</span></span></span></code></pre></div></div>
<h2 id="5-deploy-the-n8n-container">5. Deploy the n8n container</h2>
<p>This is where it all comes together. We deploy the latest n8n image and configure all secrets and environment variables, including:	Basic auth credentials, PostgreSQL connection string, Encryption key, TLS (automatically provided by Azure) and 	Static scaling (1 replica)</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-powershell">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="s2">&#34;</span><span class="se">`n</span><span class="s2">Deploying n8n container app...&#34;</span> <span class="n">-ForegroundColor</span> <span class="n">Cyan</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">az</span> <span class="n">containerapp</span> <span class="n">create</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="p">-</span><span class="n">-name</span> <span class="nv">$APP_NAME</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="p">-</span><span class="n">-resource-group</span> <span class="nv">$RG</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="p">-</span><span class="n">-environment</span> <span class="nv">$ENV_NAME</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="p">-</span><span class="n">-image</span> <span class="n">n8nio</span><span class="p">/</span><span class="n">n8n</span><span class="err">:</span><span class="n">latest</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="p">-</span><span class="n">-ingress</span> <span class="n">external</span> <span class="p">-</span><span class="n">-target-port</span> <span class="mf">5678</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="p">-</span><span class="n">-cpu</span> <span class="mf">1.0</span> <span class="p">-</span><span class="n">-memory</span> <span class="mf">2</span><span class="p">.</span><span class="n">0Gi</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="p">-</span><span class="n">-min-replicas</span> <span class="mf">1</span> <span class="p">-</span><span class="n">-max-replicas</span> <span class="mf">1</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="p">-</span><span class="n">-secrets</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">      <span class="nb">n8n-basic</span><span class="n">-password</span><span class="p">=</span><span class="s2">&#34;</span><span class="nv">$N8N_BASIC_PASSWORD</span><span class="s2">&#34;</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">      <span class="nb">n8n-encryption</span><span class="n">-key</span><span class="p">=</span><span class="s2">&#34;</span><span class="nv">$N8N_ENCRYPTION_KEY</span><span class="s2">&#34;</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">      <span class="nb">postgres-password</span><span class="p">=</span><span class="s2">&#34;</span><span class="nv">$POSTGRES_PASSWORD</span><span class="s2">&#34;</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="p">-</span><span class="n">-env-vars</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">      <span class="n">DB_TYPE</span><span class="p">=</span><span class="n">postgresdb</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">      <span class="n">DB_POSTGRESDB_HOST</span><span class="p">=</span><span class="s2">&#34;</span><span class="nv">$POSTGRES_SERVER</span><span class="s2">.postgres.database.azure.com&#34;</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">      <span class="n">DB_POSTGRESDB_PORT</span><span class="p">=</span><span class="mf">5432</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">      <span class="n">DB_POSTGRESDB_DATABASE</span><span class="p">=</span><span class="nv">$POSTGRES_DB</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">      <span class="n">DB_POSTGRESDB_USER</span><span class="p">=</span><span class="nv">$POSTGRES_USER</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">      <span class="n">DB_POSTGRESDB_PASSWORD</span><span class="p">=</span><span class="n">secretref</span><span class="err">:</span><span class="nb">postgres-password</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">      <span class="n">DB_POSTGRESDB_SSL_REJECT_UNAUTHORIZED</span><span class="p">=</span><span class="n">false</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">      <span class="n">DB_POSTGRESDB_CONNECTION_TIMEOUT</span><span class="p">=</span><span class="mf">30000</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">      <span class="n">DB_POSTGRESDB_POOL_SIZE</span><span class="p">=</span><span class="mf">5</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">      <span class="n">N8N_BASIC_AUTH_ACTIVE</span><span class="p">=</span><span class="n">true</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">      <span class="n">N8N_BASIC_AUTH_USER</span><span class="p">=</span><span class="nv">$N8N_BASIC_USER</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">      <span class="n">N8N_BASIC_AUTH_PASSWORD</span><span class="p">=</span><span class="n">secretref</span><span class="err">:</span><span class="nb">n8n-basic</span><span class="n">-password</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">      <span class="n">N8N_ENCRYPTION_KEY</span><span class="p">=</span><span class="n">secretref</span><span class="err">:</span><span class="nb">n8n-encryption</span><span class="n">-key</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">      <span class="n">N8N_PROTOCOL</span><span class="p">=</span><span class="n">https</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">      <span class="n">N8N_PORT</span><span class="p">=</span><span class="mf">5678</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">      <span class="n">N8N_DIAGNOSTICS_ENABLED</span><span class="p">=</span><span class="n">false</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">      <span class="n">N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS</span><span class="p">=</span><span class="n">true</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">      <span class="n">GENERIC_TIMEZONE</span><span class="p">=</span><span class="s2">&#34;Europe/Amsterdam&#34;</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">      <span class="n">TRUST_PROXY</span><span class="p">=</span><span class="n">true</span> <span class="p">|</span> <span class="nb">Out-Null</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="s2">&#34;✓ n8n container app deployed securely&#34;</span> <span class="n">-ForegroundColor</span> <span class="n">Green</span></span></span></code></pre></div></div>
<h2 id="6-configure-webhooks-and-url">6. Configure webhooks and URL</h2>
<p>After deployment, we extract the public FQDN and configure n8n’s internal variables like WEBHOOK_URL and N8N_HOST.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-powershell">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="c"># Get the public hostname and configure webhook URLs</span>
</span></span><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="s2">&#34;</span><span class="se">`n</span><span class="s2">Configuring webhooks...&#34;</span> <span class="n">-ForegroundColor</span> <span class="n">Cyan</span>
</span></span><span class="line"><span class="cl"><span class="nv">$APP_FQDN</span> <span class="p">=</span> <span class="n">az</span> <span class="n">containerapp</span> <span class="n">show</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="p">-</span><span class="n">-resource-group</span> <span class="nv">$RG</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="p">-</span><span class="n">-name</span> <span class="nv">$APP_NAME</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="p">-</span><span class="n">-query</span> <span class="s2">&#34;properties.configuration.ingress.fqdn&#34;</span> <span class="n">-o</span> <span class="n">tsv</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Update webhook and host configuration</span>
</span></span><span class="line"><span class="cl"><span class="n">az</span> <span class="n">containerapp</span> <span class="n">update</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="p">-</span><span class="n">-resource-group</span> <span class="nv">$RG</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="p">-</span><span class="n">-name</span> <span class="nv">$APP_NAME</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="p">-</span><span class="n">-set-env-vars</span> <span class="n">WEBHOOK_URL</span><span class="p">=</span><span class="s2">&#34;https://</span><span class="nv">$APP_FQDN</span><span class="s2">&#34;</span> <span class="n">N8N_HOST</span><span class="p">=</span><span class="s2">&#34;</span><span class="nv">$APP_FQDN</span><span class="s2">&#34;</span> <span class="p">|</span> <span class="nb">Out-Null</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="s2">&#34;✓ Webhook configuration complete&#34;</span> <span class="n">-ForegroundColor</span> <span class="n">Green</span></span></span></code></pre></div></div>
<h2 id="7-final-output-and-login-details">7. Final output and login details</h2>
<p>Summary of everything you need to access and manage the n8n instance, including login credentials.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-powershell">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="s2">&#34;</span><span class="se">`n</span><span class="s2">=== n8n Secure Deployment Complete ===&#34;</span> <span class="n">-ForegroundColor</span> <span class="n">Green</span> <span class="n">-BackgroundColor</span> <span class="n">Black</span>
</span></span><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="s2">&#34;</span><span class="se">`n</span><span class="s2">n8n URL: https://</span><span class="nv">$APP_FQDN</span><span class="s2">&#34;</span> <span class="n">-ForegroundColor</span> <span class="n">Cyan</span>
</span></span><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="s2">&#34;Username: </span><span class="nv">$N8N_BASIC_USER</span><span class="s2">&#34;</span> <span class="n">-ForegroundColor</span> <span class="n">Yellow</span>
</span></span><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="s2">&#34;Password: </span><span class="nv">$N8N_BASIC_PASSWORD</span><span class="s2">&#34;</span> <span class="n">-ForegroundColor</span> <span class="n">Yellow</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="s2">&#34;</span><span class="se">`n</span><span class="s2">PostgreSQL Details (Private Network):&#34;</span> <span class="n">-ForegroundColor</span> <span class="n">Cyan</span>
</span></span><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="s2">&#34;Server: </span><span class="nv">$POSTGRES_SERVER</span><span class="s2">.postgres.database.azure.com&#34;</span> <span class="n">-ForegroundColor</span> <span class="n">Yellow</span>
</span></span><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="s2">&#34;Database: </span><span class="nv">$POSTGRES_DB</span><span class="s2">&#34;</span> <span class="n">-ForegroundColor</span> <span class="n">Yellow</span>
</span></span><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="s2">&#34;User: </span><span class="nv">$POSTGRES_USER</span><span class="s2">&#34;</span> <span class="n">-ForegroundColor</span> <span class="n">Yellow</span>
</span></span><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="s2">&#34;Password: </span><span class="nv">$POSTGRES_PASSWORD</span><span class="s2">&#34;</span> <span class="n">-ForegroundColor</span> <span class="n">Yellow</span></span></span></code></pre></div></div>
<p>In the Azure Portal it looks like this:</p>
<h2 id="connecting-my-ai-chat-agent-to-n8n">Connecting my AI chat agent to n8n</h2>
<p>At this point in the setup, I finally had what I needed: a secure, self-hosted instance of n8n, always-on, neatly tucked into a private Azure network, and ready to receive input. But this wasn’t just about deploying a container or setting up PostgreSQL correctly. I was building the backend for something more personal, an intelligent agent that could talk to visitors on my blog, understand who I am, what I’ve worked on, and respond as if it were me.</p>
<p>That’s the vision behind <strong>ClemensGPT</strong>.</p>
<p>Inside n8n, it all starts with a workflow triggered by a webhook. This is the entry point, the moment someone on my website types a message into the chat box, and the request flows through the cloud, landing inside my n8n container. From there, the magic begins.</p>
<p>The workflow parses the message, routes it through a series of logic steps and returns a thoughtful, contextual response. The actual chat interface on the blog is nothing more than HTML and JavaScript calling the right endpoint, but behind the curtain, there’s a full orchestration engine at work.</p>
<p>This is what excites me most about building with n8n and Azure, every piece is composable, flexible, and private by design. I can iterate fast, fine-tune workflows in real time, and integrate anything from OpenAI to my own MCP server or blog metadata store.</p>
<p>The infrastructure is now invisible. All that’s left is the experience.</p>
<h2 id="conclusion">Conclusion</h2>
<p>When I started this project, I just wanted a way to play with ideas, I needed something small and cheap. But what I ended up creating is the foundation for something much bigger, an extensible, secure automation engine that powers a real-time AI assistant tied to my digital identity.</p>
<p>This wasn’t just about running n8n in a container. It was about control. About learning. About giving shape to an idea I had late one night &ldquo;<strong>what if my blog could talk back?</strong>&rdquo;</p>
<p>The beauty of this approach is that I’m not limited to just one agent. Tomorrow, I can build another focused on developer tools, or Azure Maps APIs, or even automating parts of my work. With n8n as the backend and Azure AI as the brain, the possibilities are wide open.</p>
<p>In the next post, I’ll show how I designed <strong>ClemensGPT</strong> itself, how I injected memory and personality into the responses, streamed tokens for instant feedback, and fine-tuned everything to reflect my voice. But for now, the backend is alive. The pipes are connected. The agent is listening.</p>
]]></description></item><item><title>Why VS Code + GitHub Copilot Became My Developer Cockpit</title><link>https://clemens.ms/why-vs-code--github-copilot-became-my-developer-cockpit/</link><pubDate>Tue, 29 Jul 2025 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/why-vs-code--github-copilot-became-my-developer-cockpit/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><p>The first time I connected Visual Studio Code with GitHub Copilot, I expected party tricks. I got something closer to a new way of working. It wasn’t that code appeared by magic. It was that the little frictions, like the boilerplate, the glue code, the tests I knew I should write but kept postponing, stopped dominating my day. VS Code had already been my editor of choice for its ergonomics and extensibility; Copilot turned that editor into a cockpit where intention became motion. This post is my field report: what I actually run, how I prompt, where it saves time, and where I still slow down and think.</p>
<h2 id="what-i-ask-from-an-ide">What I ask from an IDE</h2>
<p>My week crosses C# backends on .NET, Rust services and CLIs, and the occasional Zig utility when I want to get close to the metal. I need a place where navigation doesn’t melt under solutions and workspaces, where debugging is first-class for CoreCLR and native processes, where analyzers and formatters keep me honest, where I can orchestrate repeatable tasks without a pile of shell glue, and where a brand-new contributor can open the project and be productive in minutes. VS Code hits those marks out of the box; the difference after adding Copilot is that the hand-offs, like authoring tests, drafting docs, sketching refactors, writing commit messages, start to feel like a single continuous motion instead of separate chores.</p>
<h2 id="what-i-think-about-copilot">What I think about Copilot</h2>
<p>Copilot is, for me, a drafting engine with context awareness. It reads what I’m doing in the editor and offers the most likely next move. I don’t treat it as an oracle. I treat it like a sharp junior engineer who has seen a million repositories and never gets tired of typing. If my intent is vague, its suggestions are vague; if my intent is precise, its drafts become eerily on target. When I’m unsure where I’m going, I sketch types or tests first, then let Copilot fill the empty spaces. When correctness matters, I keep my hands on the wheel, review everything, and tighten the specs.</p>
<h2 id="the-setup-i-actually-use">The setup I actually use</h2>
<p>I keep VS Code lean but deliberate. <strong>Copilot</strong> and <strong>Copilot Chat</strong> are installed and enabled. For C#, the <a href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit" target="_blank" rel="noopener noreffer ">C# Dev Kit</a> and the official <a href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csharp" target="_blank" rel="noopener noreffer ">C# extension</a> give me language services, Roslyn analyzers, and a one-keystroke test runner. For Rust, <a href="https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer" target="_blank" rel="noopener noreffer ">rust-analyzer</a> and <a href="https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb" target="_blank" rel="noopener noreffer ">CodeLLDB</a> give me the right balance of hints and stepping power; for Zig, the <a href="https://marketplace.visualstudio.com/items?itemName=ziglang.vscode-zig" target="_blank" rel="noopener noreffer ">Zig extension</a> keep the feedback loop tight. I treat VS Code’s tasks and launch configurations as part of the codebase because “press F5 and go” is culture, not an accident.</p>
<h2 id="prompts-that-pull-their-weight">Prompts that pull their weight</h2>
<p>The prompts that consistently work are specific, grounded in the files that are open, and framed with constraints. I’ll say, “Given CsvToNdjson.cs, rewrite the parser to avoid allocations using spans; keep the public signature, add tests for malformed rows.” Or, “In src/main.rs, add Serde models and stream CSV to NDJSON with proper error handling; no panics; propose a minimal diff.” If I’m refactoring, I’ll ask for a before/after with a rule like “no side effects; inject I/O; keep pure functions testable.” When a test fails, I paste the stack trace and ask for an explanation and a minimal patch that doesn’t change the public API. The more I emphasize small functions, exhaustive types, and early returns, the more the drafts read like my code instead of generic code.</p>
<h2 id="examples">Examples</h2>
<p>I like examples that stand up in production. A simple but representative task is a streaming transform that reads a large CSV, converts amounts, and writes NDJSON (It’s a format where each line is a separate JSON object). It’s the kind of thing with a right answer but lots of ceremony.</p>
<h3 id="c">C#</h3>
<p>I want this to run in constant memory, avoid unnecessary allocations, and write one JSON object per line. I’ll keep the parser simple and deterministic and use <code>System.Threading.Channels</code> to bound the pipeline so one slow stage does not drown the rest.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-csharp">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="c1">// src/CsvToNdjson.cs</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">System.Buffers.Text</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">System.Text</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">System.Text.Json</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">System.Threading.Channels</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">static</span> <span class="k">class</span> <span class="nc">CsvToNdjson</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="k">record</span> <span class="nc">InputRow</span><span class="p">(</span><span class="kt">string</span> <span class="n">Id</span><span class="p">,</span> <span class="kt">int</span> <span class="n">AmountCents</span><span class="p">,</span> <span class="kt">string</span> <span class="n">Currency</span><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="kd">public</span> <span class="kt">bool</span> <span class="n">IsHighValue</span><span class="p">(</span><span class="kt">int</span> <span class="n">threshold</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="n">AmountCents</span> <span class="p">&gt;=</span> <span class="n">threshold</span><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></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">async</span> <span class="n">Task</span> <span class="n">ConvertAsync</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="kt">string</span> <span class="n">csvPath</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="kt">string</span> <span class="n">outPath</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="kt">int</span> <span class="n">threshold</span> <span class="p">=</span> <span class="m">50_000</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">CancellationToken</span> <span class="n">ct</span> <span class="p">=</span> <span class="k">default</span><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="kt">var</span> <span class="n">channel</span> <span class="p">=</span> <span class="n">Channel</span><span class="p">.</span><span class="n">CreateBounded</span><span class="p">&lt;</span><span class="n">InputRow</span><span class="p">&gt;(</span><span class="k">new</span> <span class="n">BoundedChannelOptions</span><span class="p">(</span><span class="m">8192</span><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="n">SingleReader</span> <span class="p">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">SingleWriter</span> <span class="p">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">FullMode</span> <span class="p">=</span> <span class="n">BoundedChannelFullMode</span><span class="p">.</span><span class="n">Wait</span>
</span></span><span class="line"><span class="cl">        <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">producer</span> <span class="p">=</span> <span class="n">Task</span><span class="p">.</span><span class="n">Run</span><span class="p">(</span><span class="kd">async</span> <span class="p">()</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">await</span> <span class="k">using</span> <span class="nn">var</span> <span class="n">fs</span> <span class="p">=</span> <span class="n">File</span><span class="p">.</span><span class="n">OpenRead</span><span class="p">(</span><span class="n">csvPath</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">using</span> <span class="nn">var</span> <span class="n">sr</span> <span class="p">=</span> <span class="k">new</span> <span class="n">StreamReader</span><span class="p">(</span><span class="n">fs</span><span class="p">,</span> <span class="n">Encoding</span><span class="p">.</span><span class="n">UTF8</span><span class="p">,</span> <span class="n">detectEncodingFromByteOrderMarks</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="n">bufferSize</span><span class="p">:</span> <span class="m">1</span> <span class="p">&lt;&lt;</span> <span class="m">16</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="kt">string?</span> <span class="n">header</span> <span class="p">=</span> <span class="k">await</span> <span class="n">sr</span><span class="p">.</span><span class="n">ReadLineAsync</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">header</span> <span class="k">is</span> <span class="kc">null</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="k">while</span> <span class="p">(!</span><span class="n">sr</span><span class="p">.</span><span class="n">EndOfStream</span><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="n">ct</span><span class="p">.</span><span class="n">ThrowIfCancellationRequested</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">                <span class="kt">var</span> <span class="n">line</span> <span class="p">=</span> <span class="k">await</span> <span class="n">sr</span><span class="p">.</span><span class="n">ReadLineAsync</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="kt">string</span><span class="p">.</span><span class="n">IsNullOrWhiteSpace</span><span class="p">(</span><span class="n">line</span><span class="p">))</span> <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                <span class="c1">// naive split; swap with a CSV reader if your data contains quoted commas</span>
</span></span><span class="line"><span class="cl">                <span class="kt">var</span> <span class="n">parts</span> <span class="p">=</span> <span class="n">line</span><span class="p">.</span><span class="n">Split</span><span class="p">(</span><span class="sc">&#39;,&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="n">parts</span><span class="p">.</span><span class="n">Length</span> <span class="p">&lt;</span> <span class="m">3</span><span class="p">)</span> <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(!</span><span class="kt">int</span><span class="p">.</span><span class="n">TryParse</span><span class="p">(</span><span class="n">parts</span><span class="p">[</span><span class="m">1</span><span class="p">],</span> <span class="k">out</span> <span class="kt">var</span> <span class="n">amount</span><span class="p">))</span> <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="kt">var</span> <span class="n">row</span> <span class="p">=</span> <span class="k">new</span> <span class="n">InputRow</span><span class="p">(</span><span class="n">parts</span><span class="p">[</span><span class="m">0</span><span class="p">],</span> <span class="n">amount</span><span class="p">,</span> <span class="n">parts</span><span class="p">[</span><span class="m">2</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">                <span class="k">await</span> <span class="n">channel</span><span class="p">.</span><span class="n">Writer</span><span class="p">.</span><span class="n">WriteAsync</span><span class="p">(</span><span class="n">row</span><span class="p">,</span> <span class="n">ct</span><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></span><span class="line"><span class="cl">            <span class="n">channel</span><span class="p">.</span><span class="n">Writer</span><span class="p">.</span><span class="n">Complete</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span> <span class="n">ct</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">await</span> <span class="k">using</span> <span class="nn">var</span> <span class="n">outStream</span> <span class="p">=</span> <span class="n">File</span><span class="p">.</span><span class="n">Create</span><span class="p">(</span><span class="n">outPath</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">await</span> <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">row</span> <span class="k">in</span> <span class="n">channel</span><span class="p">.</span><span class="n">Reader</span><span class="p">.</span><span class="n">ReadAllAsync</span><span class="p">(</span><span class="n">ct</span><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="kt">var</span> <span class="n">json</span> <span class="p">=</span> <span class="n">JsonSerializer</span><span class="p">.</span><span class="n">Serialize</span><span class="p">(</span><span class="k">new</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">id</span> <span class="p">=</span> <span class="n">row</span><span class="p">.</span><span class="n">Id</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">amountCents</span> <span class="p">=</span> <span class="n">row</span><span class="p">.</span><span class="n">AmountCents</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">currency</span> <span class="p">=</span> <span class="n">row</span><span class="p">.</span><span class="n">Currency</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">isHighValue</span> <span class="p">=</span> <span class="n">row</span><span class="p">.</span><span class="n">IsHighValue</span><span class="p">(</span><span class="n">threshold</span><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></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">bytes</span> <span class="p">=</span> <span class="n">Encoding</span><span class="p">.</span><span class="n">UTF8</span><span class="p">.</span><span class="n">GetBytes</span><span class="p">(</span><span class="n">json</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">await</span> <span class="n">outStream</span><span class="p">.</span><span class="n">WriteAsync</span><span class="p">(</span><span class="n">bytes</span><span class="p">,</span> <span class="n">ct</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">await</span> <span class="n">outStream</span><span class="p">.</span><span class="n">WriteAsync</span><span class="p">(</span><span class="k">new</span> <span class="kt">byte</span><span class="p">[]</span> <span class="p">{</span> <span class="p">(</span><span class="kt">byte</span><span class="p">)</span><span class="sc">&#39;\n&#39;</span> <span class="p">},</span> <span class="n">ct</span><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></span><span class="line"><span class="cl">        <span class="k">await</span> <span class="n">producer</span><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></code></pre></div></div>
<p>I like my tests to be small and expressive. <a href="https://xunit.net/" target="_blank" rel="noopener noreffer ">xUnit</a> gives me exactly that, and Copilot happily drafts the first pass that I then tighten.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-csharp">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="c1">// tests/CsvToNdjsonTests.cs</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">System.IO</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">System.Text</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">Xunit</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">CsvToNdjsonTests</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="na">    [Fact]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">async</span> <span class="n">Task</span> <span class="n">Converts_And_Flags_High_Value</span><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="kt">var</span> <span class="n">csv</span> <span class="p">=</span> <span class="s">&#34;id,amount,currency\nA,50000,EUR\nB,49999,USD\n&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">tmpCsv</span> <span class="p">=</span> <span class="n">Path</span><span class="p">.</span><span class="n">GetTempFileName</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">tmpOut</span> <span class="p">=</span> <span class="n">Path</span><span class="p">.</span><span class="n">GetTempFileName</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="k">await</span> <span class="n">File</span><span class="p">.</span><span class="n">WriteAllTextAsync</span><span class="p">(</span><span class="n">tmpCsv</span><span class="p">,</span> <span class="n">csv</span><span class="p">,</span> <span class="n">Encoding</span><span class="p">.</span><span class="n">UTF8</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">await</span> <span class="n">CsvToNdjson</span><span class="p">.</span><span class="n">ConvertAsync</span><span class="p">(</span><span class="n">tmpCsv</span><span class="p">,</span> <span class="n">tmpOut</span><span class="p">,</span> <span class="n">threshold</span><span class="p">:</span> <span class="m">50_000</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">lines</span> <span class="p">=</span> <span class="k">await</span> <span class="n">File</span><span class="p">.</span><span class="n">ReadAllLinesAsync</span><span class="p">(</span><span class="n">tmpOut</span><span class="p">,</span> <span class="n">Encoding</span><span class="p">.</span><span class="n">UTF8</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">Assert</span><span class="p">.</span><span class="n">Contains</span><span class="p">(</span><span class="s">&#34;\&#34;isHighValue\&#34;:true&#34;</span><span class="p">,</span> <span class="n">lines</span><span class="p">[</span><span class="m">0</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">        <span class="n">Assert</span><span class="p">.</span><span class="n">Contains</span><span class="p">(</span><span class="s">&#34;\&#34;isHighValue\&#34;:false&#34;</span><span class="p">,</span> <span class="n">lines</span><span class="p">[</span><span class="m">1</span><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></code></pre></div></div>
<p>For debugging, I keep a simple <code>launch.json</code> so hitting F5 under CoreCLR does the right thing, with symbols ready and just-my-code enabled.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-json">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="c1">// .vscode/launch.json (excerpt)
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;0.2.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;configurations&#34;</span><span class="p">:</span> <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="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;.NET Launch&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;coreclr&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;request&#34;</span><span class="p">:</span> <span class="s2">&#34;launch&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;program&#34;</span><span class="p">:</span> <span class="s2">&#34;${workspaceFolder}/bin/Debug/net9.0/YourApp.dll&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;cwd&#34;</span><span class="p">:</span> <span class="s2">&#34;${workspaceFolder}&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;console&#34;</span><span class="p">:</span> <span class="s2">&#34;integratedTerminal&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;justMyCode&#34;</span><span class="p">:</span> <span class="kc">true</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></code></pre></div></div>
<p>I also let Copilot draft analyzers and ruleset updates when a pattern repeats in reviews; it won’t replace human judgment, but it saves me from rewriting the same diagnostic boilerplate.</p>
<h3 id="rust">Rust</h3>
<p><a href="https://www.rust-lang.org" target="_blank" rel="noopener noreffer ">Rust</a> is where I go when I want sharp edges and strong guarantees. I still keep I/O injectable and avoid panics for expected errors. Copilot is good at filling out the pattern once I’ve defined the types and the contract.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-toml">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="c"># Cargo.toml (excerpt)</span>
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">package</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;csv_ndjson&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">version</span> <span class="p">=</span> <span class="s2">&#34;0.1.0&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">edition</span> <span class="p">=</span> <span class="s2">&#34;2021&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">dependencies</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">csv</span> <span class="p">=</span> <span class="s2">&#34;1&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">serde</span> <span class="p">=</span> <span class="p">{</span> <span class="nx">version</span> <span class="p">=</span> <span class="s2">&#34;1&#34;</span><span class="p">,</span> <span class="nx">features</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;derive&#34;</span><span class="p">]</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="nx">serde_json</span> <span class="p">=</span> <span class="s2">&#34;1&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">thiserror</span> <span class="p">=</span> <span class="s2">&#34;1&#34;</span></span></span></code></pre></div></div>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-rust">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="c1">// src/main.rs
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">use</span><span class="w"> </span><span class="n">csv</span>::<span class="n">StringRecord</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">use</span><span class="w"> </span><span class="n">serde</span>::<span class="n">Serialize</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">use</span><span class="w"> </span><span class="n">std</span>::<span class="p">{</span><span class="n">fs</span>::<span class="n">File</span><span class="p">,</span><span class="w"> </span><span class="n">io</span>::<span class="n">Write</span><span class="p">,</span><span class="w"> </span><span class="n">path</span>::<span class="n">PathBuf</span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">use</span><span class="w"> </span><span class="n">thiserror</span>::<span class="n">Error</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="cp">#[derive(Debug, Error)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">enum</span> <span class="nc">AppError</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="cp">#[error(</span><span class="s">&#34;io: {0}&#34;</span><span class="cp">)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Io</span><span class="p">(</span><span class="cp">#[from]</span><span class="w"> </span><span class="n">std</span>::<span class="n">io</span>::<span class="n">Error</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="cp">#[error(</span><span class="s">&#34;csv: {0}&#34;</span><span class="cp">)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Csv</span><span class="p">(</span><span class="cp">#[from]</span><span class="w"> </span><span class="n">csv</span>::<span class="n">Error</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="cp">#[error(</span><span class="s">&#34;invalid row: {0}&#34;</span><span class="cp">)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Invalid</span><span class="p">(</span><span class="nb">String</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="cp">#[derive(Serialize, Debug)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">struct</span> <span class="nc">OutRow</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="n">id</span>: <span class="nb">String</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">amount_cents</span>: <span class="kt">u64</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">currency</span>: <span class="nb">String</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">is_high_value</span>: <span class="kt">bool</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="k">fn</span> <span class="nf">map_record</span><span class="p">(</span><span class="n">rec</span>: <span class="kp">&amp;</span><span class="nc">StringRecord</span><span class="p">,</span><span class="w"> </span><span class="n">threshold</span>: <span class="kt">u64</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="n">OutRow</span><span class="p">,</span><span class="w"> </span><span class="n">AppError</span><span class="o">&gt;</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="kd">let</span><span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">rec</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="mi">0</span><span class="p">).</span><span class="n">ok_or_else</span><span class="p">(</span><span class="o">||</span><span class="w"> </span><span class="n">AppError</span>::<span class="n">Invalid</span><span class="p">(</span><span class="s">&#34;missing id&#34;</span><span class="p">.</span><span class="n">into</span><span class="p">()))</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">amount_str</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">rec</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="mi">1</span><span class="p">).</span><span class="n">ok_or_else</span><span class="p">(</span><span class="o">||</span><span class="w"> </span><span class="n">AppError</span>::<span class="n">Invalid</span><span class="p">(</span><span class="s">&#34;missing amount&#34;</span><span class="p">.</span><span class="n">into</span><span class="p">()))</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">currency</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">rec</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="mi">2</span><span class="p">).</span><span class="n">ok_or_else</span><span class="p">(</span><span class="o">||</span><span class="w"> </span><span class="n">AppError</span>::<span class="n">Invalid</span><span class="p">(</span><span class="s">&#34;missing currency&#34;</span><span class="p">.</span><span class="n">into</span><span class="p">()))</span><span class="o">?</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">let</span><span class="w"> </span><span class="n">amount_cents</span>: <span class="kt">u64</span> <span class="o">=</span><span class="w"> </span><span class="n">amount_str</span><span class="p">.</span><span class="n">parse</span><span class="p">().</span><span class="n">map_err</span><span class="p">(</span><span class="o">|</span><span class="n">_</span><span class="o">|</span><span class="w"> </span><span class="n">AppError</span>::<span class="n">Invalid</span><span class="p">(</span><span class="s">&#34;bad amount&#34;</span><span class="p">.</span><span class="n">into</span><span class="p">()))</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nb">Ok</span><span class="p">(</span><span class="n">OutRow</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="n">id</span>: <span class="nc">id</span><span class="p">.</span><span class="n">to_string</span><span class="p">(),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">amount_cents</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">currency</span>: <span class="nc">currency</span><span class="p">.</span><span class="n">to_string</span><span class="p">(),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">is_high_value</span>: <span class="nc">amount_cents</span><span class="w"> </span><span class="o">&gt;=</span><span class="w"> </span><span class="n">threshold</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></span><span class="line"><span class="cl"><span class="w"></span><span class="k">fn</span> <span class="nf">run</span><span class="p">(</span><span class="n">input</span>: <span class="nc">PathBuf</span><span class="p">,</span><span class="w"> </span><span class="n">output</span>: <span class="nc">PathBuf</span><span class="p">,</span><span class="w"> </span><span class="n">threshold</span>: <span class="kt">u64</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span><span class="w"> </span><span class="n">AppError</span><span class="o">&gt;</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="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">rdr</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">csv</span>::<span class="n">Reader</span>::<span class="n">from_path</span><span class="p">(</span><span class="n">input</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">out</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">File</span>::<span class="n">create</span><span class="p">(</span><span class="n">output</span><span class="p">)</span><span class="o">?</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="k">for</span><span class="w"> </span><span class="n">result</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">rdr</span><span class="p">.</span><span class="n">records</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="kd">let</span><span class="w"> </span><span class="n">rec</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">result</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">row</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">map_record</span><span class="p">(</span><span class="o">&amp;</span><span class="n">rec</span><span class="p">,</span><span class="w"> </span><span class="n">threshold</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// let json = serde_json::to_string(&amp;row).expect(&#34;serialize&#34;);
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">json</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">serde_json</span>::<span class="n">to_string</span><span class="p">(</span><span class="o">&amp;</span><span class="n">row</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="fm">writeln!</span><span class="p">(</span><span class="n">out</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;{json}&#34;</span><span class="p">)</span><span class="o">?</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="nb">Ok</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="k">fn</span> <span class="nf">main</span><span class="p">()</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span><span class="w"> </span><span class="n">AppError</span><span class="o">&gt;</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="c1">// hardcode paths for brevity; wire clap/argp in real life
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">    </span><span class="n">run</span><span class="p">(</span><span class="s">&#34;input.csv&#34;</span><span class="p">.</span><span class="n">into</span><span class="p">(),</span><span class="w"> </span><span class="s">&#34;out.json&#34;</span><span class="p">.</span><span class="n">into</span><span class="p">(),</span><span class="w"> </span><span class="mi">50_000</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></span></code></pre></div></div>
<p>And the test that keeps me honest lives right next to it. Copilot will generate something close to this once it sees the function signature; I always tighten the assertions and add failure cases.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-rust">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="c1">// src/lib.rs (optional if you split logic for testing)
</span></span></span><span class="line"><span class="cl"><span class="c1">// tests/transform.rs
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="cp">#[cfg(test)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">mod</span> <span class="nn">tests</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="k">use</span><span class="w"> </span><span class="k">super</span>::<span class="o">*</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">use</span><span class="w"> </span><span class="n">std</span>::<span class="n">io</span>::<span class="n">Write</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">use</span><span class="w"> </span><span class="n">tempfile</span>::<span class="n">NamedTempFile</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="cp">#[test]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">fn</span> <span class="nf">flags_high_value_correctly</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="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">csv</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">NamedTempFile</span>::<span class="n">new</span><span class="p">().</span><span class="n">unwrap</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="fm">writeln!</span><span class="p">(</span><span class="n">csv</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;id,amount,currency&#34;</span><span class="p">).</span><span class="n">unwrap</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="fm">writeln!</span><span class="p">(</span><span class="n">csv</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;A,50000,EUR&#34;</span><span class="p">).</span><span class="n">unwrap</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="fm">writeln!</span><span class="p">(</span><span class="n">csv</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;B,49999,USD&#34;</span><span class="p">).</span><span class="n">unwrap</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">let</span><span class="w"> </span><span class="n">out</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">NamedTempFile</span>::<span class="n">new</span><span class="p">().</span><span class="n">unwrap</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="n">run</span><span class="p">(</span><span class="n">csv</span><span class="p">.</span><span class="n">path</span><span class="p">().</span><span class="n">into</span><span class="p">(),</span><span class="w"> </span><span class="n">out</span><span class="p">.</span><span class="n">path</span><span class="p">().</span><span class="n">into</span><span class="p">(),</span><span class="w"> </span><span class="mi">50_000</span><span class="p">).</span><span class="n">unwrap</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">let</span><span class="w"> </span><span class="n">content</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">std</span>::<span class="n">fs</span>::<span class="n">read_to_string</span><span class="p">(</span><span class="n">out</span><span class="p">.</span><span class="n">path</span><span class="p">()).</span><span class="n">unwrap</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">lines</span>: <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">_</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">content</span><span class="p">.</span><span class="n">lines</span><span class="p">().</span><span class="n">collect</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="fm">assert!</span><span class="p">(</span><span class="n">lines</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">contains</span><span class="p">(</span><span class="s">&#34;</span><span class="se">\&#34;</span><span class="s">is_high_value</span><span class="se">\&#34;</span><span class="s">:true&#34;</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="fm">assert!</span><span class="p">(</span><span class="n">lines</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="n">contains</span><span class="p">(</span><span class="s">&#34;</span><span class="se">\&#34;</span><span class="s">is_high_value</span><span class="se">\&#34;</span><span class="s">:false&#34;</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></span></code></pre></div></div>
<p>For stepping through Rust, CodeLLDB in VS Code remains my default. I keep a launch configuration that points at the produced binary so I don’t think about paths while I’m thinking about state.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-json">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="c1">// .vscode/launch.json (rust excerpt)
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;0.2.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;configurations&#34;</span><span class="p">:</span> <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="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;Debug Rust binary&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;lldb&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;request&#34;</span><span class="p">:</span> <span class="s2">&#34;launch&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;program&#34;</span><span class="p">:</span> <span class="s2">&#34;${workspaceFolder}/target/debug/csv_ndjson&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;args&#34;</span><span class="p">:</span> <span class="p">[],</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;cwd&#34;</span><span class="p">:</span> <span class="s2">&#34;${workspaceFolder}&#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></code></pre></div></div>
<h3 id="zig">Zig</h3>
<p><a href="https://ziglang.org" target="_blank" rel="noopener noreffer ">Zig</a> is refreshing when I want predictable performance and explicit control. Copilot is still helpful for the scaffolding, but I rely on the standard library for the important parts and keep errors explicit.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-zig">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-zig" data-lang="zig"><span class="line"><span class="cl"><span class="c1">// src/main.zig
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">const</span><span class="w"> </span><span class="n">std</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">@import</span><span class="p">(</span><span class="s">&#34;std&#34;</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="kr">pub</span><span class="w"> </span><span class="k">fn</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="o">!</span><span class="kt">void</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="kr">var</span><span class="w"> </span><span class="n">gpa</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="n">heap</span><span class="p">.</span><span class="nf">GeneralPurposeAllocator</span><span class="p">(.{}){};</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">defer</span><span class="w"> </span><span class="n">_</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">gpa</span><span class="p">.</span><span class="nf">deinit</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">const</span><span class="w"> </span><span class="n">allocator</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">gpa</span><span class="p">.</span><span class="nf">allocator</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="kr">var</span><span class="w"> </span><span class="n">args</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="n">process</span><span class="p">.</span><span class="nf">args</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">_</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="n">args</span><span class="p">.</span><span class="nf">next</span><span class="p">();</span><span class="w"> </span><span class="c1">// program name
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">    </span><span class="kr">const</span><span class="w"> </span><span class="n">in_path</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">args</span><span class="p">.</span><span class="nf">next</span><span class="p">()</span><span class="w"> </span><span class="k">orelse</span><span class="w"> </span><span class="s">&#34;input.csv&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">const</span><span class="w"> </span><span class="n">out_path</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">args</span><span class="p">.</span><span class="nf">next</span><span class="p">()</span><span class="w"> </span><span class="k">orelse</span><span class="w"> </span><span class="s">&#34;out.json&#34;</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="kr">const</span><span class="w"> </span><span class="n">threshold</span><span class="o">:</span><span class="w"> </span><span class="kt">u64</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">50_000</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="kr">var</span><span class="w"> </span><span class="n">infile</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="n">fs</span><span class="p">.</span><span class="nf">cwd</span><span class="p">().</span><span class="nf">openFile</span><span class="p">(</span><span class="n">in_path</span><span class="p">,</span><span class="w"> </span><span class="p">.{</span><span class="w"> </span><span class="p">.</span><span class="n">read</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">true</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="k">defer</span><span class="w"> </span><span class="n">infile</span><span class="p">.</span><span class="nf">close</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">var</span><span class="w"> </span><span class="n">outfile</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="n">fs</span><span class="p">.</span><span class="nf">cwd</span><span class="p">().</span><span class="nf">createFile</span><span class="p">(</span><span class="n">out_path</span><span class="p">,</span><span class="w"> </span><span class="p">.{</span><span class="w"> </span><span class="p">.</span><span class="n">write</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"> </span><span class="p">.</span><span class="n">truncate</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">true</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="k">defer</span><span class="w"> </span><span class="n">outfile</span><span class="p">.</span><span class="nf">close</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="kr">var</span><span class="w"> </span><span class="n">br</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="n">io</span><span class="p">.</span><span class="nf">bufferedReader</span><span class="p">(</span><span class="n">infile</span><span class="p">.</span><span class="nf">reader</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">var</span><span class="w"> </span><span class="n">in_stream</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">br</span><span class="p">.</span><span class="nf">reader</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="kr">var</span><span class="w"> </span><span class="n">line_buf</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="nf">ArrayList</span><span class="p">(</span><span class="kt">u8</span><span class="p">).</span><span class="nf">init</span><span class="p">(</span><span class="n">allocator</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">defer</span><span class="w"> </span><span class="n">line_buf</span><span class="p">.</span><span class="nf">deinit</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">// read header
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="w">    </span><span class="n">_</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="nf">readLineAlloc</span><span class="p">(</span><span class="n">in_stream</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">line_buf</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="k">while</span><span class="w"> </span><span class="p">(</span><span class="k">try</span><span class="w"> </span><span class="nf">readLineAlloc</span><span class="p">(</span><span class="n">in_stream</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">line_buf</span><span class="p">))</span><span class="w"> </span><span class="o">|</span><span class="n">line</span><span class="o">|</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="kr">var</span><span class="w"> </span><span class="n">it</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="n">mem</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="kt">u8</span><span class="p">,</span><span class="w"> </span><span class="n">line</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;,&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kr">const</span><span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">it</span><span class="p">.</span><span class="nf">next</span><span class="p">()</span><span class="w"> </span><span class="k">orelse</span><span class="w"> </span><span class="k">continue</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kr">const</span><span class="w"> </span><span class="n">amount_s</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">it</span><span class="p">.</span><span class="nf">next</span><span class="p">()</span><span class="w"> </span><span class="k">orelse</span><span class="w"> </span><span class="k">continue</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kr">const</span><span class="w"> </span><span class="n">currency</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">it</span><span class="p">.</span><span class="nf">next</span><span class="p">()</span><span class="w"> </span><span class="k">orelse</span><span class="w"> </span><span class="k">continue</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="kr">const</span><span class="w"> </span><span class="n">amount</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="n">fmt</span><span class="p">.</span><span class="nf">parseInt</span><span class="p">(</span><span class="kt">u64</span><span class="p">,</span><span class="w"> </span><span class="n">amount_s</span><span class="p">,</span><span class="w"> </span><span class="mi">10</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kr">const</span><span class="w"> </span><span class="n">is_high</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">amount</span><span class="w"> </span><span class="o">&gt;=</span><span class="w"> </span><span class="n">threshold</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="k">try</span><span class="w"> </span><span class="n">outfile</span><span class="p">.</span><span class="nf">writer</span><span class="p">().</span><span class="nf">print</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34;{{</span><span class="se">\&#34;</span><span class="s">id</span><span class="se">\&#34;</span><span class="s">:</span><span class="se">\&#34;</span><span class="s">{s}</span><span class="se">\&#34;</span><span class="s">,</span><span class="se">\&#34;</span><span class="s">amountCents</span><span class="se">\&#34;</span><span class="s">:{d},</span><span class="se">\&#34;</span><span class="s">currency</span><span class="se">\&#34;</span><span class="s">:</span><span class="se">\&#34;</span><span class="s">{s}</span><span class="se">\&#34;</span><span class="s">,</span><span class="se">\&#34;</span><span class="s">isHighValue</span><span class="se">\&#34;</span><span class="s">:{s}}}</span><span class="se">\n</span><span class="s">&#34;</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 class="n">id</span><span class="p">,</span><span class="w"> </span><span class="n">amount</span><span class="p">,</span><span class="w"> </span><span class="n">currency</span><span class="p">,</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">is_high</span><span class="p">)</span><span class="w"> </span><span class="s">&#34;true&#34;</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="s">&#34;false&#34;</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="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="k">fn</span><span class="w"> </span><span class="nf">readLineAlloc</span><span class="p">(</span><span class="n">reader</span><span class="o">:</span><span class="w"> </span><span class="n">anytype</span><span class="p">,</span><span class="w"> </span><span class="n">buf</span><span class="o">:</span><span class="w"> </span><span class="o">*</span><span class="n">std</span><span class="p">.</span><span class="nf">ArrayList</span><span class="p">(</span><span class="kt">u8</span><span class="p">))</span><span class="w"> </span><span class="o">!?</span><span class="p">[]</span><span class="kt">u8</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="n">buf</span><span class="p">.</span><span class="nf">clearRetainingCapacity</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">var</span><span class="w"> </span><span class="n">stream</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">reader</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">const</span><span class="w"> </span><span class="n">nl</span><span class="o">:</span><span class="w"> </span><span class="kt">u8</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="se">&#39;\n&#39;</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="k">while</span><span class="w"> </span><span class="p">(</span><span class="kc">true</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="kr">var</span><span class="w"> </span><span class="n">byte</span><span class="o">:</span><span class="w"> </span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="kt">u8</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">undefined</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kr">const</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="n">stream</span><span class="p">.</span><span class="nf">read</span><span class="p">(</span><span class="n">byte</span><span class="p">[</span><span class="mi">0</span><span class="p">..]);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">n</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="k">break</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">byte</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="n">nl</span><span class="p">)</span><span class="w"> </span><span class="k">break</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">try</span><span class="w"> </span><span class="n">buf</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">byte</span><span class="p">[</span><span class="mi">0</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="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">buf</span><span class="p">.</span><span class="n">items</span><span class="p">.</span><span class="n">len</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="kc">null</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="n">buf</span><span class="p">.</span><span class="n">items</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></span></code></pre></div></div>
<p>Zig’s test blocks live right next to the code. I’ll often write a tiny line-split test to pin behavior before letting Copilot fill the pattern everywhere else.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-zig">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-zig" data-lang="zig"><span class="line"><span class="cl"><span class="k">test</span><span class="w"> </span><span class="s">&#34;parse int safely&#34;</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="kr">const</span><span class="w"> </span><span class="n">parsed</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="n">fmt</span><span class="p">.</span><span class="nf">parseInt</span><span class="p">(</span><span class="kt">u64</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;50000&#34;</span><span class="p">,</span><span class="w"> </span><span class="mi">10</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">try</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="n">testing</span><span class="p">.</span><span class="nf">expectEqual</span><span class="p">(</span><span class="nb">@as</span><span class="p">(</span><span class="kt">u64</span><span class="p">,</span><span class="w"> </span><span class="mi">50000</span><span class="p">),</span><span class="w"> </span><span class="n">parsed</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></span></code></pre></div></div>
<h2 id="debugging-with-ai-in-the-loop">Debugging with AI in the loop</h2>
<p>Debugging is where the integration matters most. I run tests or start the program under the debugger, capture the failing stack trace, and paste it into Copilot Chat with a request to explain what happened and propose a minimal fix that avoids changing public APIs. Because the code and errors are already open in the editor, the back-and-forth stays grounded. Conditional breakpoints and logpoints are my default because they don’t require code changes, and I keep a handful of watch expressions to track the objects that tend to go sideways. In .NET, I keep symbol loading fast and source link enabled; in Rust and Zig, I let CodeLLDB or the C/C++ debugger step through optimized builds when I’m chasing heisenbugs.</p>
<h2 id="exercising-apis-and-wiring-ci">Exercising APIs and wiring CI</h2>
<p>I like having an <code>api.http</code> file that hits real endpoints during development. It doubles as documentation you can execute, and Copilot is surprisingly good at filling in token flows and error cases once it sees the pattern. On the CI side, I let Copilot draft a minimal pipeline—dotnet test with caches for NuGet, cargo test with a sensible Rust toolchain matrix, and a simple Zig build—then I tune it to fit the project’s real constraints. The speed-up is not in writing YAML I could write myself; it’s in not context-switching away from the code that actually matters.</p>
<h2 id="pull-requests-reviews-and-change-management">Pull requests, reviews, and change management</h2>
<p>A good pull request is a story with a beginning, middle, and end. Inside VS Code, I use the GitHub Pull Requests extension to live inside that story, and I ask Copilot to help with the narrative parts. It will summarize a diff, call out potential risks, and suggest missing tests; it will also draft a conventional commit message with a “Why” section that I can edit down to the truth. The point isn’t to outsource judgment. It’s to spend judgment on design and correctness instead of on verb forms and checklists.</p>
<h2 id="closing-thoughts">Closing thoughts</h2>
<p>VS Code without Copilot is a great editor. VS Code with Copilot is, for me, a force multiplier that shortens the path between intention and impact. The magic isn’t that it “writes code for you.” The magic is that the editor becomes a place where you can describe what you mean in the language of your project, and the ceremony melts away. If you try this stack, start small: pick a real task, state your intent as precisely as you can, and let the assistant draft the parts that don’t deserve creative energy. Keep the parts that do.</p>
]]></description></item><item><title>Create Apps without code using GitHub Spark</title><link>https://clemens.ms/create-apps-without-code-using-github-spark/</link><pubDate>Mon, 28 Jul 2025 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/create-apps-without-code-using-github-spark/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><p>Imagine this: you work at a company, and you have a clear idea for a web app, something like a custom expense tracking tool that fits exactly the way your team works. You’ve tried off-the-shelf products, but they always miss the mark. The alternative, building your own application, is usually time-consuming, expensive, and requires getting developers involved early on. But what if you could prototype your idea, adjust the UI, change the data model, and publish it, all without writing a single line of code?</p>
<p><strong>That’s exactly what GitHub Spark makes possible</strong></p>
<h2 id="what-is-github-spark">What is GitHub Spark</h2>
<p><a href="https://github.com/spark" target="_blank" rel="noopener noreffer ">GitHub Spark</a> is a new tool in the GitHub Copilot ecosystem, designed for anyone who wants to bring software ideas to life without being a developer. It leverages AI to translate natural language descriptions, yes, just plain text, into a fully functional web application. You describe what you want, and Spark generates the user interface, the data layer, and even deployment instructions. It doesn’t just simulate an app, it builds it.</p>
<h2 id="describe-your-idea-in-plain-text">Describe Your Idea in Plain Text</h2>
<p>The process is surprisingly intuitive. You open GitHub Spark and simply write what you want the app to do: “I want an expense tracker with categories, approval flows, and a dashboard for summaries.” Within minutes, Spark will scaffold your application, including forms, tables, styling, and storage. You’ll see it running right away. This is not a fake mockup, this is a live prototype. And if something isn’t quite right? Just type what you want to change. “Make the buttons blue.” “Add a notes field to each expense.” “Group the expenses by project.” Spark listens, understands, and adapts in real time.</p>
<h2 id="customize-everything-visually">Customize Everything Visually</h2>
<p>It’s not only functional, it’s visual. You can fine-tune the app’s look and feel without design tools or CSS. Change the typography, update the color palette, adjust the border radius. Want to change how the app stores or displays data? That’s editable too. The whole experience feels like talking to a smart assistant who just happens to be a front-end dev, a back-end dev, and a designer rolled into one.</p>
<h2 id="speed-and-accessibility-for-all-creators">Speed and Accessibility for All Creators</h2>
<p>At its core, GitHub Spark is about speed and accessibility. For non-technical creators, this is an incredibly powerful way to test ideas, validate concepts, or even deploy simple tools that are good enough to use right away. And for technical teams, it’s a great way to get ahead, kickstarting the UI or getting a working data schema in place before engineering resources are allocated.</p>
<h2 id="developers">Developers?</h2>
<p>GitHub Spark isn’t going to replace skilled developers for complex enterprise-grade systems. It’s not meant to. But what <em>is</em> impressive is how quickly and accurately it lets you build a real, working prototype. You don’t just explain the idea, you see it, use it, and iterate on it. That alone makes it one of the most interesting AI-powered tools GitHub has launched since Copilot.</p>
<h2 id="ready-to-try-spark">Ready to Try Spark?</h2>
<p>If you’re curious, Spark is now available in public preview, and you can read more about it on GitHub’s official documentation. But the best way to understand Spark is to try it. Think of an idea you’ve had for a while, open Spark, and just start describing it.</p>
<p>You’ll be surprised how fast you can go from “I wish I had an app for this…” to “Here’s the link to the <a href="https://expenseflow-tracker--cschotte.github.app/" target="_blank" rel="noopener noreffer ">expense app</a>, try it out.”.</p>
]]></description></item><item><title>What are AI Agents and how Agentic AI transforms your Business</title><link>https://clemens.ms/what-are-ai-agents-and-how-agentic-ai-transforms-your-business/</link><pubDate>Thu, 24 Jul 2025 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/what-are-ai-agents-and-how-agentic-ai-transforms-your-business/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><p>AI is no longer an abstract promise of the future. It’s here, embedded into enterprise workflows, products, and decision-making processes. From Microsoft Copilot to ChatGPT and domain-specific assistants, businesses are adopting AI at an unprecedented pace. But too often, &ldquo;AI&rdquo; is used as a catch-all term for a wide range of technologies. To lead the next transformation wave, organizations must move beyond generic AI adoption and toward <em>agentic AI</em>, a more autonomous, goal-driven form of AI that’s ready to take on real work.</p>
<p>Agentic AI brings intelligence into action. It refers to AI systems that don’t just respond, they act! These systems exhibit goal-directed behavior, operate autonomously, make decisions, use tools, and apply reasoning and planning across time and tasks. In essence, an <em>AI Agent</em> is a digital co-worker, one that doesn’t need micromanagement, doesn&rsquo;t take lunch breaks, and adapts to dynamic environments with memory and purpose.</p>
<p>This evolution is more than hype. It’s the operational shift from static machine learning models to AI-driven workflows that <em>reason</em>, <em>remember</em>, and <em>execute</em>.</p>
<h2 id="what-is-an-ai-agent-technically">What Is an AI Agent, Technically?</h2>
<p>At its core, an AI Agent is a form of intelligent orchestrator. Unlike traditional automation scripts or rule-based bots, an AI Agent is built with modular cognitive capabilities. It uses a Large Language Model (LLM), or for more resource-efficient use cases, a Small Language Model (SLM), as its reasoning engine. This &lsquo;brain&rsquo; is surrounded by memory (both short-term and long-term), a set of tools (think APIs, databases, services), and a controller that manages objectives, context, and planning.</p>
<p>In this architecture, the agent receives a goal or task, either via human input, another system, or even another agent and autonomously decides how to accomplish it. That includes invoking tools through Model Context Protocol (MCP) servers, storing and retrieving information from (vector) databases, interacting with external APIs, and even collaborating with other AI Agents in what’s known as Agent-to-Agent (A2A) workflows.</p>
<p>This level of autonomy is not trivial. It requires careful design choices: prompt engineering, tool chaining, vector search optimization, and guardrails to ensure safety, compliance, and operational efficiency. These agents are not &ldquo;one prompt away&rdquo; systems, they are designed, configured, and iterated like software components. And yes, there are platforms that let non-technical users describe what they want and auto-generate a lightweight AI agent. But in my experience leading enterprise-scale transformations, these tools rarely deliver the robustness, observability, and cost-efficiency needed for production-grade systems.</p>
<h1 id="agentic-ai-in-action">Agentic AI in Action</h1>
<p>Let’s leave the abstract and dive into a real-world example: logistics (knowing me this should be not a surprise). Suppose you run a mid-sized delivery company with hundreds of daily shipments, multiple depots, and a fleet of trucks and drivers. Every evening, your dispatch team prepares the next day’s delivery plan; routes, assignments, departure times. It’s a repetitive, constraint-heavy problem. Now imagine replacing or augmenting that function with an AI Agent.</p>
<p>The AI Agent wakes up every evening with one objective: generate the most cost-efficient, constraint-satisfying delivery plan. It pulls data from internal systems: driver availability, vehicle health, customer delivery windows, and package locations. It uses tools like <a href="https://www.azuremaps.com/" target="_blank" rel="noopener noreffer ">Azure Maps</a> and <a href="https://clemens.ms/multi-itinerary-optimization/" target="_blank" rel="noopener noreffer ">NVIDIA cuOpt</a> to optimize routing based on real-time traffic, road conditions, and fuel efficiency. It then notifies customers of expected delivery times, and if something changes, driver calls in sick, a truck breaks down, or a high-priority shipment arrives late, it recalculates and adapts without needing manual intervention.</p>
<p>This isn’t just about automation. <strong>It’s autonomy</strong>. It’s a system that understands goals, reasons about alternatives, and executes actions independently. And the impact? Reduced operational overhead, improved delivery SLAs, and real-time resilience. That’s the promise of agentic AI and it’s already happening.</p>
<h2 id="behind-the-scenes">Behind the Scenes</h2>
<p>Memory is the difference between a chatbot and a cognitive agent. Without memory, an AI system is like a goldfish, it forgets everything after each prompt. To build agents that can handle dynamic workflows, context shifts, and strategic planning, we must give them memory, both episodic (short-term) and semantic (long-term).</p>
<p>This is where vector databases become critical. Unlike traditional databases, vector stores allow semantic retrieval. Text, images, code, or other high-dimensional data are encoded into vector embeddings, which can then be queried by similarity. In AI agents, this enables contextual recall: “What happened in the last planning session?”, “What did the customer ask three days ago?”, or “What were the top-performing routes last month?”</p>
<p>Even more importantly, vector databases allow grounding LLMs with external knowledge. One of the biggest weaknesses of LLMs is their tendency to hallucinate or forget facts. By querying a vector store and injecting relevant context into the prompt, agents can generate responses that are both accurate and relevant to the domain. This approach, often called <em>Retrieval-Augmented Generation (RAG)</em>, is essential for enterprise reliability.</p>
<h2 id="what-this-means-for-the-enterprise">What this means for the Enterprise</h2>
<p>Agentic AI is not a lab experiment. It’s a production-ready pattern that can improve cost-efficiency, accelerate decision-making, and unlock new capabilities across industries, from logistics and supply chain to customer service, healthcare, and compliance.</p>
<p>But building effective AI Agents is not just a matter of plugging an LLM into a workflow. It requires experience in systems design, cloud architecture, vector semantics, tool integration, and AI safety. As someone who has worked across both Microsoft’s Azure Maps platform and real-world customer implementations, I’ve seen firsthand what works and what doesn’t. AI Agents and agentic AI are not just buzzwords. They are the future of enterprise intelligence.</p>
]]></description></item><item><title>Enabling Geospatial Intelligence in LLMs with Azure Maps and MCP</title><link>https://clemens.ms/enabling-geospatial-intelligence-in-llms-with-azure-maps-and-mcp/</link><pubDate>Mon, 21 Jul 2025 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/enabling-geospatial-intelligence-in-llms-with-azure-maps-and-mcp/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><p>In today’s AI era, you’ve likely interacted with Microsoft Copilot, ChatGPT, or Claude.ai, tools powered by advanced <strong>Large Language Models</strong> (LLMs). These models excel at understanding and generating human-like text based on vast amounts of training data. However, while LLMs are impressive at reasoning and answering general questions, they fall short when it comes to performing real-world tasks or retrieving live, domain-specific information.</p>
<p>This is where the <strong>Model Context Protocol</strong> (MCP) comes in.</p>
<p>MCP extends the capabilities of LLMs by connecting them to structured, real-time, and authoritative data sources, essentially giving them access to <strong>tools</strong> and APIs that can “do things” instead of just “talk about things.” One compelling use case is enriching LLMs with geospatial intelligence by integrating Azure Maps into an MCP server.</p>
<p>Let’s consider a practical example, you ask Copilot:</p>
<blockquote>
<p>“Which neighborhood does this address Kerkstraat 10, Amsterdam belongs to?”</p></blockquote>
<p>Out-of-the-box, the model may not know the answer. At best, it might offer a hallucinated guess based on its training data, which is often outdated or incomplete for such hyper-local questions.</p>
<p>By integrating Azure Maps into an MCP server, you empower the LLM to perform (reverse) geocoding to get address details, Identify neighborhoods, postal codes, or administrative regions, visualize or reason about spatial data like routes, boundaries, or real-time traffic details.</p>
<p>With an MCP server acting as a bridge between the LLM and Azure Maps, the process looks like this:</p>
<ol>
<li><strong>User Query:</strong> The user (or AI agent) asks a geographically relevant question.</li>
<li><strong>MCP Routing:</strong> The MCP server receives the query and routes it to a plugin/tool backed by Azure Maps APIs.</li>
<li><strong>Azure Maps Execution:</strong> The server makes the API call (e.g., reverse geocoding, route calculation, etc).</li>
<li><strong>LLM Response Generation:</strong> The MCP server returns structured results to the LLM, which incorporates it into a coherent and accurate response.</li>
</ol>
<p>Integrating Azure Maps into an MCP server is surprisingly straightforward, especially when using <strong>.NET</strong> and <strong>Azure Functions</strong>. In this blog, I’ll walk you through the core setup to expose Azure Maps as a tool to an LLM via MCP.</p>
<p>While implementing a Model Context Protocol (MCP) server normally requires strict adherence to the <a href="https://modelcontextprotocol.io/" target="_blank" rel="noopener noreffer ">MCP protocol</a> specifications, Microsoft has made it much easier by providing a prebuilt extension: <code>Microsoft.Azure.Functions.Worker.Extensions.Mcp</code>. This NuGet package abstracts away most of the boilerplate, so you can focus on defining the tools you want to expose and simply wire them to Azure Maps APIs.
We’ll start by creating a standard Azure Functions project using <strong>Visual Studio</strong> or <strong>Visual Studio Code</strong>.</p>
<h2 id="step-1-set-up-the-project">Step 1: Set Up the Project</h2>
<p>Create a new Azure Functions project targeting .NET 9 or newer and add the following NuGet package:</p>
<p><code>dotnet add package Microsoft.Azure.Functions.Worker.Extensions.Mcp</code></p>
<h2 id="step-2-configure-mcp">Step 2: Configure MCP</h2>
<p>Here’s a sample <code>Program.cs</code> that sets up the MCP tool metadata and registers an <code>HttpClient</code> for making requests to Azure Maps:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-csharp">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">builder</span> <span class="p">=</span> <span class="n">FunctionsApplication</span><span class="p">.</span><span class="n">CreateBuilder</span><span class="p">(</span><span class="n">args</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">builder</span><span class="p">.</span><span class="n">ConfigureFunctionsWebApplication</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="n">builder</span><span class="p">.</span><span class="n">EnableMcpToolMetadata</span><span class="p">();</span> <span class="c1">// Enables tool registration metadata</span>
</span></span><span class="line"><span class="cl"><span class="n">builder</span><span class="p">.</span><span class="n">ConfigureMcpTool</span><span class="p">(</span><span class="s">&#34;Azure Maps Tool&#34;</span><span class="p">);</span> <span class="c1">// Register the tool name exposed to LLMs</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">builder</span><span class="p">.</span><span class="n">Services</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">AddHttpClient</span><span class="p">(</span><span class="s">&#34;AzureMaps&#34;</span><span class="p">)</span> <span class="c1">// Named http client for Azure Maps</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">AddStandardResilienceHandler</span><span class="p">();</span> <span class="c1">// Optional: adds resilience policies</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">builder</span><span class="p">.</span><span class="n">Build</span><span class="p">().</span><span class="n">Run</span><span class="p">();</span></span></span></code></pre></div></div>
<h2 id="step-3-implement-an-mcp-tool">Step 3: Implement an MCP Tool</h2>
<p>Let’s expose one function for this example: <strong>geocoding a location</strong> (i.e., converting an address or landmark into geographic coordinates) using Azure Maps.</p>
<p>Here’s the function code:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-csharp">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">AzureMapsTools</span><span class="p">(</span><span class="n">IHttpClientFactory</span> <span class="n">httpClientFactory</span><span class="p">,</span> <span class="n">IConfiguration</span> <span class="n">configuration</span><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="na">    [Function(nameof(GeocodeLocation))]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;</span> <span class="n">GeocodeLocation</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// This attribute registers the function as an MCP tool trigger.</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// The name and description help users understand what the tool does.</span>
</span></span><span class="line"><span class="cl"><span class="na">        [McpToolTrigger(
</span></span></span><span class="line"><span class="cl"><span class="na">            &#34;geocode_location&#34;,
</span></span></span><span class="line"><span class="cl"><span class="na">            &#34;Geocode a location, such as an address or landmark name using Azure Maps&#34;)]</span> <span class="n">ToolInvocationContext</span> <span class="n">context</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="na">        [McpToolProperty(
</span></span></span><span class="line"><span class="cl"><span class="na">            &#34;location&#34;,
</span></span></span><span class="line"><span class="cl"><span class="na">            &#34;string&#34;,
</span></span></span><span class="line"><span class="cl"><span class="na">            &#34;address or landmark name&#34;)]</span> <span class="kt">string</span> <span class="n">location</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="c1">// Get the Azure Maps subscription key from configuration.</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// This key is needed to authenticate requests to the Azure Maps API.</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// If the key is missing, throw an error to alert the developer.</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">subscriptionKey</span> <span class="p">=</span> <span class="n">configuration</span><span class="p">[</span><span class="s">&#34;AZURE_MAPS_SUBSCRIPTION_KEY&#34;</span><span class="p">]</span> 
</span></span><span class="line"><span class="cl">            <span class="p">??</span> <span class="k">throw</span> <span class="k">new</span> <span class="n">InvalidOperationException</span><span class="p">(</span><span class="s">&#34;Azure Maps subscription key not configured&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// Build the URL for the Azure Maps geocoding API.</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// The query parameter contains the location, and the subscription key authenticates the request.</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">url</span> <span class="p">=</span> <span class="s">$&#34;https://atlas.microsoft.com/geocode?api-version=2025-01-01&amp;query={Uri.EscapeDataString(location)}&amp;subscription-key={subscriptionKey}&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// Create an HTTP client to send the request.</span>
</span></span><span class="line"><span class="cl">        <span class="k">using</span> <span class="nn">var</span> <span class="n">httpClient</span> <span class="p">=</span> <span class="n">httpClientFactory</span><span class="p">.</span><span class="n">CreateClient</span><span class="p">(</span><span class="s">&#34;AzureMaps&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// Send the GET request to the Azure Maps API.</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">response</span> <span class="p">=</span> <span class="k">await</span> <span class="n">httpClient</span><span class="p">.</span><span class="n">GetAsync</span><span class="p">(</span><span class="n">url</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// Check if the response was successful.</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// If not, read the error message and throw an exception.</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(!</span><span class="n">response</span><span class="p">.</span><span class="n">IsSuccessStatusCode</span><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="kt">var</span> <span class="n">error</span> <span class="p">=</span> <span class="k">await</span> <span class="n">response</span><span class="p">.</span><span class="n">Content</span><span class="p">.</span><span class="n">ReadAsStringAsync</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">            <span class="k">throw</span> <span class="k">new</span> <span class="n">HttpRequestException</span><span class="p">(</span><span class="s">$&#34;Azure Maps API failed with status {response.StatusCode}: {error}&#34;</span><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></span><span class="line"><span class="cl">        <span class="c1">// Read the result from the response as a string (JSON format).</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">await</span> <span class="n">response</span><span class="p">.</span><span class="n">Content</span><span class="p">.</span><span class="n">ReadAsStringAsync</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// Return the geocoding result to the caller.</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">result</span><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></code></pre></div></div>
<p>The function returns the full Azure Maps response in JSON format, which can then be parsed or summarized by the LLM in its reply.</p>
<p>By combining the <em>real-time spatial intelligence</em> of Azure Maps with the <em>reasoning capabilities of large language models</em>, you move beyond passive AI into the realm of <em>actionable AI agents</em>, where systems not only understand queries but execute real-world tasks in response.</p>
<p>You can easily add more tools such as reverse geocoding, route calculation, POI search, or even time zone conversion, all backed by the same MCP server architecture.</p>
]]></description></item><item><title>Enhancing Logistics with Azure Maps and NVIDIA cuOpt for Multi-Itinerary Optimization</title><link>https://clemens.ms/multi-itinerary-optimization/</link><pubDate>Wed, 22 May 2024 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/multi-itinerary-optimization/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><p>The logistics industry constantly seeks efficiency in routing vehicles to deliver goods. This is where the <strong>Vehicle Routing Problem</strong> (VRP) and <strong>Pickup and Delivery Problems</strong> (PDP) come into the picture as both are sophisticated extensions of the classic Traveling Salesperson Problem (TSP). The TSP poses a simple yet challenging question: <em>&ldquo;What is the most efficient route that visits each destination once and returns to the starting point?&rdquo;</em> This problem is not just academic; it has practical applications in logistics, where optimizing routes can lead to substantial savings in time and costs.</p>
<p>So, what is needed to tackle this problem? At its simplest incarnation, you need reliable inputs that provide estimates as to how long each route to a location will take and then you need to know how to apply the rest of the constraints like driver availability, time taken at each location, opening hours, working hours, demand, capacity and more. In the solution we will review here, Azure Maps provides the input and NVIDIA cuOpt will apply the constraints for optimization.</p>
<h2 id="the-flexibility-of-azure-maps">The Flexibility of Azure Maps</h2>
<p><a href="https://azuremaps.com/" target="_blank" rel="noopener noreffer ">Azure Maps</a> offers, besides mapping, also an <strong>advanced routing engine</strong>, tailored for both cars and commercial trucks, complete with a <a href="https://learn.microsoft.com/en-us/rest/api/maps/route/post-route-matrix" target="_blank" rel="noopener noreffer ">Route Matrix API</a> and traffic and accident APIs what are part of the routing engine as well. Learn <a href="https://learn.microsoft.com/en-us/azure/azure-maps/about-azure-maps" target="_blank" rel="noopener noreffer ">here</a> more about what Azure Maps can do for you.</p>
<h2 id="the-power-of-nvidia-cuopt">The Power of NVIDIA cuOpt</h2>
<p>Enter <a href="https://www.nvidia.com/en-us/ai-data-science/products/cuopt/" target="_blank" rel="noopener noreffer ">NVIDIA cuOpt</a>, a state-of-the-art, <strong>GPU-accelerated</strong> engine designed for tackling complex routing challenges. It leverages parallel heuristics to accelerate solutions to VRP, PDP, and other related optimization problems. NVIDIA cuOpt is versatile, accommodating various constraints such as vehicle capacities, delivery time windows, and even the intricacies of drivers&rsquo; shifts and breaks.</p>
<h2 id="solving-tsp-with-azure-maps-and-nvidia-cuopt">Solving TSP with Azure Maps and NVIDIA cuOpt</h2>
<p>Imagine you&rsquo;re tasked with coordinating package deliveries from multiple depots, utilizing a fleet of vehicles with varying capacities, to a diverse set of customers with unique requirements. Azure Maps and NVIDIA cuOpt emerge as your allies in this complex scenario.</p>
<h3 id="preparation-gathering-essential-data">Preparation: Gathering Essential Data</h3>
<p>Before you begin with route optimization, you&rsquo;ll need to collect some fundamental data:</p>
<ul>
<li><strong>Depot Locations:</strong> Where your journey begins.</li>
<li><strong>Customer Drop-off Locations:</strong> Including details like opening hours and dwell time.</li>
<li><strong>Vehicle Specifications:</strong> Capacity, starting, and ending locations.</li>
<li><strong>Driver Details:</strong> Working hours, breaks, vehicle assignments, and costs.</li>
</ul>
<h3 id="step-one-constructing-a-cost-and-travel-time-matrix">Step One: Constructing a Cost and Travel Time Matrix</h3>
<p>Using <a href="https://learn.microsoft.com/en-us/rest/api/maps/route/get-route-matrix" target="_blank" rel="noopener noreffer ">Azure Maps Matrix APIs</a>, you can calculate a comprehensive matrix that outlines the &lsquo;cost&rsquo; in meters and travel time in seconds between every depot and drop-off location. For instance, with two depots and eight drop-off points, you&rsquo;d create a 10x10 matrix. Each location serves as both an origin and a destination, resulting in a matrix where the distance and time to the same location are zero—since they are, naturally, identical.</p>
<table>
  <thead>
      <tr>
          <th>matrix</th>
          <th>Depot 1</th>
          <th>Depot 2</th>
          <th>Stop 1</th>
          <th>Stop 2</th>
          <th>Stop 3</th>
          <th>&hellip;</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Depot 1</td>
          <td>0</td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
      </tr>
      <tr>
          <td>Depot 2</td>
          <td></td>
          <td>0</td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
      </tr>
      <tr>
          <td>Stop 1</td>
          <td></td>
          <td></td>
          <td>0</td>
          <td></td>
          <td></td>
          <td></td>
      </tr>
      <tr>
          <td>Stop 2</td>
          <td></td>
          <td></td>
          <td></td>
          <td>0</td>
          <td></td>
          <td></td>
      </tr>
      <tr>
          <td>Stop 3</td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td>0</td>
          <td></td>
      </tr>
      <tr>
          <td>&hellip;</td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td>0</td>
      </tr>
  </tbody>
</table>
<p>We have developed a <a href="https://samples.azuremaps.com/?sample=multi-itinerary-optimization" target="_blank" rel="noopener noreffer ">Multi-Itinerary Optimization Sample</a> to get you started quickly, the core of this sample is this function:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-javascript">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="c1">// Itinerary Optimization function
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">async</span> <span class="kd">function</span> <span class="nx">itineraryOptimizationClicked</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Show loading icon
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">showLoadingIcon</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Clear the route data source
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">routeDataSource</span><span class="p">.</span><span class="nx">clear</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// We need all coordinates together depots + stops
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kr">const</span> <span class="nx">allCoordinates</span> <span class="o">=</span> <span class="p">[...</span><span class="nx">depots</span><span class="p">,</span> <span class="p">...</span><span class="nx">stops</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Get the Azure Maps Matrix results for all coordinates
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kr">const</span> <span class="nx">matrix</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">getMatrixAsync</span><span class="p">(</span><span class="nx">allCoordinates</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// We use the NVIDIA cuOpt optimizer service to calculate the Multi-Itinerary Optimization
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kr">const</span> <span class="nx">solver</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">getOptimizedRouteAsync</span><span class="p">(</span><span class="nx">matrix</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Render the route on the map for each vehicle using Azure Maps Route API
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">renderRoutes</span><span class="p">(</span><span class="nx">allCoordinates</span><span class="p">,</span> <span class="nx">solver</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Hide loading icon
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">showLoadingIcon</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p>The complete Multi-Itinerary Optimization Sample <a href="https://github.com/Azure-Samples/AzureMapsCodeSamples/blob/main/Samples/REST%20Services/MIO/mio.html" target="_blank" rel="noopener noreffer ">source code</a> can be found on the <a href="https://github.com/Azure-Samples/AzureMapsCodeSamples" target="_blank" rel="noopener noreffer ">Azure Maps Code Samples GitHub</a> page.</p>
<h3 id="step-two-determining-the-optimal-delivery-sequence">Step Two: Determining the Optimal Delivery Sequence</h3>
<p>With the matrix data in hand, it&rsquo;s time to decide the delivery sequence. This is where <a href="https://docs.nvidia.com/cuopt/" target="_blank" rel="noopener noreffer ">NVIDIA cuOpt</a> shines, optimizing the order of delivery for each vehicle based on capacity, demand, and operational hours—without needing any sensitive location or customer data, thus addressing privacy concerns.</p>
<blockquote>
<p>See our <a href="https://learn.microsoft.com/azure/azure-maps/itinerary-optimization-service" target="_blank" rel="noopener noreffer ">how-to guide</a> how to set up and create a solution with NVIDIA cuOpt and Azure Maps.</p></blockquote>
<h3 id="step-three-real-world-route-mapping">Step Three: Real-World Route Mapping</h3>
<p>Once NVIDIA cuOpt has determined the optimal delivery order, the final step is to map the actual routes using Azure Maps Routing APIs. This process translates the optimized sequence into actionable routes for each vehicle.</p>
<p>In a hypothetical scenario with four vehicles serving two depots and eight drop-off locations, the outcome might look like this:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><pre tabindex="0"><code>Depot 1: Vehicle A -&gt; Stop 1 -&gt; Stop 3 -&gt; Stop 5 -&gt; Depot 1
Depot 1: Vehicle B -&gt; Stop 2 -&gt; Stop 4 -&gt; Stop 6 -&gt; Depot 1
Depot 2: Vehicle C -&gt; Stop 7 -&gt; Stop 8 -&gt; Depot 2
Depot 2: Vehicle D -&gt; Stop 9 -&gt; Stop 10 -&gt; Depot 2</code></pre></div>
<p><a href="https://samples.azuremaps.com/?sample=multi-itinerary-optimization" target="_blank" rel="noopener noreffer "></a></p>
<p><strong>Are you ready to deliver?</strong> Get started with the Azure Maps and NVIDIA cuOpt to optimize your delivery needs. Read our <a href="https://learn.microsoft.com/azure/azure-maps/itinerary-optimization-service" target="_blank" rel="noopener noreffer ">how-to guide</a> and start delivering to your customers quicker.</p>
<blockquote>
<p>This blog post was initially written by me for the <a href="https://blog.azuremaps.com" target="_blank" rel="noopener noreffer ">Azure Maps Tech Blog</a>.</p></blockquote>
]]></description></item><item><title>Introducing the Unified Azure Maps Experience</title><link>https://clemens.ms/azure-maps-unification/</link><pubDate>Tue, 21 May 2024 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/azure-maps-unification/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><p>We are thrilled to announce the unification of <strong>Bing Maps for Enterprise</strong> (BME) with <strong>Azure Maps</strong>, marking a significant milestone in our geospatial services at Microsoft. <a href="https://azuremaps.com/" target="_blank" rel="noopener noreffer ">Azure Maps</a> now boasts a robust stack of geospatial offerings, leveraging the powerful capabilities of Microsoft Maps, which also drives <a href="https://bingmaps.com/" target="_blank" rel="noopener noreffer ">Bing Maps</a> (our consumer maps experience). Over the past year, our team has dedicated significant time and effort to combine the strengths of Bing Maps for Enterprise into Azure Maps, enhancing our global quality and coverage.</p>
<p>One of the major enhancements is the adoption of vector tiles in Azure Maps for a more responsive map experience. When utilizing Azure Maps in your solutions, you not only leverage the security and compliance advantages of Azure but also benefit from the extensive quality and coverage provided by <a href="https://www.microsoft.com/maps" target="_blank" rel="noopener noreffer ">Microsoft Maps</a>.</p>
<p>This unification ensures that users of Azure Maps receive a comprehensive mapping solution backed by the unparalleled strengths of Azure&rsquo;s infrastructure, Microsoft Maps&rsquo; data quality and coverage, and many of the same advanced geospatial capabilities that Bing Maps for Enterprise customers depend on. We are excited about the opportunities this integration presents and look forward to continuing to deliver innovative mapping solutions to our customers worldwide.</p>
<p>Azure Maps has many of the same features that BME customers have come to rely on. Nevertheless, this unification also introduces exciting new features to Azure Maps, such as <a href="https://learn.microsoft.com/rest/api/maps/weather" target="_blank" rel="noopener noreffer ">weather APIs</a>, <a href="https://learn.microsoft.com/azure/azure-maps/about-creator" target="_blank" rel="noopener noreffer ">private indoor maps</a>, <a href="https://learn.microsoft.com/azure/azure-maps/azure-maps-authentication" target="_blank" rel="noopener noreffer ">multiple authentication methods</a>, <a href="https://learn.microsoft.com/en-us/rest/api/maps/geolocation" target="_blank" rel="noopener noreffer ">geolocation service</a>, and robust <strong>privacy</strong> and <strong>compliance</strong> benefits.</p>
<h2 id="ready-to-make-the-move">Ready to Make the Move?</h2>
<p>For customers that are using Bing Maps for Enterprise and are migrating over to Azure Maps, some development will be needed. To help you in this transition period, we have written <a href="https://learn.microsoft.com/en-us/azure/azure-maps/migrate-from-bing-maps" target="_blank" rel="noopener noreffer ">migration documents</a> for our REST APIs and as well for the Azure Maps web control. Also, a good start is our <a href="https://samples.azuremaps.com" target="_blank" rel="noopener noreffer ">Azure Maps samples</a> site where you can find not only samples for many scenarios, but also the source code.</p>
<p>More resources about Azure Maps can be found here:</p>
<ul>
<li><a href="https://docs.azuremaps.com" target="_blank" rel="noopener noreffer ">Azure Maps Documenation</a></li>
<li><a href="https://samples.azuremaps.com" target="_blank" rel="noopener noreffer ">Azure Maps Samples</a></li>
<li><a href="https://blog.azuremaps.com" target="_blank" rel="noopener noreffer ">Azure Maps Blog</a></li>
<li><a href="https://learn.microsoft.com/en-us/answers/tags/209/azure-maps" target="_blank" rel="noopener noreffer ">Microsoft Q&amp;A for Azure Maps</a></li>
</ul>
]]></description></item><item><title>What is it to be a Program Manager at Microsoft?</title><link>https://clemens.ms/program-manager/</link><pubDate>Thu, 11 Apr 2024 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/program-manager/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><p>Have you ever wondered what it&rsquo;s like to be a <strong>Program Manager</strong> (PM) at <a href="https://www.microsoft.com/" target="_blank" rel="noopener noreffer ">Microsoft</a>? As someone who often fields this question, I&rsquo;d like to offer some insights into the fascinating world of <strong>product development</strong> and <strong>customer-centric decision-making</strong> that defines the role of a PM at one of the world&rsquo;s leading tech companies.</p>
<p>At Microsoft, our mission revolves around creating products and services that address real-world needs and deliver tangible value to our users. As a Program Manager, my primary responsibility is to delve into the evolving landscape of customer demands and technological advancements to identify opportunities for enhancing our products.</p>
<p>One of the key aspects of the PM role is conducting thorough research to understand what new features or improvements customers are seeking. This involves not only listening to direct customer feedback but also analyzing market trends and competitive landscapes. While it&rsquo;s essential to prioritize features that align with customer needs, we also need to consider the business impact and feasibility of each enhancement.</p>
<p>It&rsquo;s not just about fulfilling every customer request; it&rsquo;s about strategically investing our resources where they will have the most significant impact. This means evaluating the potential financial returns of a feature against the cost of development and deployment. If a proposed feature only caters to a small subset of users, we need to assess whether it aligns with our broader strategic goals and market positioning.</p>
<p>Collaboration is at the heart of everything we do at Microsoft. As a PM, I work closely with <strong>Product Marketing Managers</strong> (PMMs) to ensure that our product roadmap aligns with market demand and consumer behavior. Customer studies and meetings play a crucial role in gathering insights into the challenges users face and the solutions they&rsquo;re seeking.</p>
<p>Once a feature has been conceptualized and scoped, it&rsquo;s time to hand it over to the <strong>Product Manager</strong> for inclusion in the product roadmap. This collaborative effort involves engaging with <strong>engineering teams</strong> to ensure that the proposed features are technically feasible and align with the overall product vision.</p>
<p>On a typical day, I engage in frequent meetings with my fellow PMs to discuss ongoing projects and align our strategies. Cross-team collaboration is essential, especially in complex product ecosystems like <a href="https://azuremaps.com/" target="_blank" rel="noopener noreffer ">Azure Maps</a>, where integration with other Microsoft products is commonplace.</p>
<p>Being a Program Manager at Microsoft is both challenging and rewarding. It requires a blend of technical expertise, strategic thinking, and a deep understanding of customer needs. By constantly iterating on our products and embracing a customer-centric approach, we strive to deliver innovative solutions that empower users and drive business growth. If you&rsquo;re passionate about technology and thrive in a fast-paced, collaborative environment, a <a href="https://jobs.careers.microsoft.com/" target="_blank" rel="noopener noreffer ">career as a Program Manager at Microsoft</a> might be the perfect fit for you.</p>
]]></description></item><item><title>Help customers find your business with the Azure Maps Store Locator</title><link>https://clemens.ms/azure-maps-store-locator/</link><pubDate>Mon, 16 Oct 2023 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/azure-maps-store-locator/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><h2 id="maximize-visibility">Maximize Visibility</h2>
<p>In today&rsquo;s digital age, the visibility of your business is paramount. Once you&rsquo;ve captured customer interest online, the next crucial step is guiding them to your physical storefronts. <a href="https://github.com/Azure-Samples/Azure-Maps-Locator" target="_blank" rel="noopener noreffer ">Azure Maps Store Locator</a> streamlines this journey, offering an interactive and intuitive experience that leads customers right to your doorstep.</p>
<h2 id="simplifying-store-discovery">Simplifying Store Discovery</h2>
<p>Creating a basic store locator using Azure Maps is already a straightforward task, involving the loading of store locations onto a map and potentially setting up a simple search functionality. However, for larger organizations managing thousands of locations and requiring advanced filtering options, a more sophisticated solution is essential. Fortunately, the Azure Maps Store Locator, combining the power of various Azure services, caters precisely to these needs.</p>
<h2 id="enhance-your-locator-experience">Enhance Your Locator Experience</h2>
<p>Imagine a tool that effortlessly connects potential customers to the nearest branch of your business, tailored to their specific needs. Whether they&rsquo;re searching for a particular service or other points of interest, <a href="https://github.com/Azure-Samples/Azure-Maps-Locator" target="_blank" rel="noopener noreffer ">Azure Maps Store Locator</a> is the user-friendly and adaptable tool you need. It&rsquo;s backed by a comprehensive management system, enabling you to create a rich locator experience.</p>
<h2 id="feature-rich-platform">Feature-Rich Platform</h2>
<p>Azure Maps Store Locator boasts a wide array of features to improve your location-based offerings:</p>
<ul>
<li><strong>Store Locator Backend</strong>: Integrates REST APIs and a Store Locator Web Control for seamless management.</li>
<li><strong>Autocomplete Search</strong>: Quickly find store names, addresses, POIs, or zip codes.</li>
<li><strong>Scalability</strong>: Manages over 10,000 locations without a hitch.</li>
<li><strong>Proximity Insights</strong>: View nearby stores and distance metrics.</li>
<li><strong>Location-Based Search</strong>: Searches can be performed based on the user&rsquo;s current location.</li>
<li><strong>Travel Time Estimates</strong>: Provides estimated travel times for various modes of transport.</li>
<li><strong>Comprehensive Store Details</strong>: Access store information, directions, and more through interactive popups.</li>
<li><strong>Dynamic Filtering</strong>: Users can filter stores based on specific features.</li>
<li><strong>Individual Store Pages</strong>: Delve into what each store offers with detailed embedded maps.</li>
<li><strong>Security</strong>: Employs Microsoft Entra ID for secure access to the location management system.</li>
<li><strong>Rich Data</strong>: Store details include location, hours, photos, and the option to add custom features.</li>
<li><strong>Accessibility</strong>: Features speech recognition and other accessibility enhancements.</li>
<li><strong>Effortless Deployment</strong>: Easily deploy within your Azure ecosystem.</li>
</ul>
<h2 id="quick-setup-guide">Quick Setup Guide</h2>
<p>Deploying Azure Maps Store Locator is straightforward:</p>
<ol>
<li><strong>Azure Subscription</strong>: Confirm you have an Azure subscription. If not, obtain one for <a href="https://azure.microsoft.com/free/" target="_blank" rel="noopener noreffer ">free</a> at the official Azure website.</li>
<li><strong>Azure Shell Access</strong>: Sign in to Azure Shell. <a href="https://shell.azure.com/" target="_blank" rel="noopener noreffer ">https://shell.azure.com/</a></li>
<li><strong>Deployment</strong>: Run the provided PowerShell script to install the Azure Maps Store Locator.</li>
</ol>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-powershell">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="nb">iex </span><span class="p">(</span><span class="nb">iwr </span><span class="s2">&#34;https://samples.azuremaps.com/storelocator/deploy.ps1&#34;</span><span class="p">).</span><span class="n">Content</span></span></span></code></pre></div></div>
<p>Integrating the store locator into your website requires some HTML and JavaScript to call the store locator backend REST APIs. Once implemented, the solution is yours to modify and tailor the source code in your Azure Maps Store Locator according to your specific needs.</p>
<p>The Azure Maps Store Locator empowers you to create and maintain an intuitive location-based search experience to delight your customers. Enhance your online presence today with the power of Azure Maps!</p>
<p>You find the Azure Maps Store Locator source code on <a href="https://github.com/Azure-Samples/Azure-Maps-Locator" target="_blank" rel="noopener noreffer ">GitHub</a>.</p>
]]></description></item><item><title>There is a New Style of Maps Across Microsoft</title><link>https://clemens.ms/there-is-a-new-style-of-maps-across-microsoft/</link><pubDate>Wed, 05 Apr 2023 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/there-is-a-new-style-of-maps-across-microsoft/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><p>We are excited to announce that we have developed a <strong>new and improved style</strong> for our maps across Microsoft. <a href="https://azuremaps.com/" target="_blank" rel="noopener noreffer ">Azure Maps</a> now utilizes the same <strong>vector tiles</strong> and <strong>satellite imagery</strong> directly as our other Microsoft mapping platforms, including our <a href="https://bingmaps.com/" target="_blank" rel="noopener noreffer ">Bing Maps</a> consumer site and more. This updated style introduces a fresh <strong>cartographic identity</strong> across Microsoft, focusing on enhancing usability, information clarity, and aesthetic appeal.</p>
<h2 id="key-features-of-the-new-style">Key Features of the New Style</h2>
<ol>
<li><strong>Style Choices</strong>: As before, Azure Maps allows you and your customers to choose from several styles, such as:
<ul>
<li><strong>Road</strong></li>
<li><strong>Night</strong></li>
<li><strong>Hybrid</strong></li>
<li><strong>Grayscale</strong> (dark and light)</li>
<li><strong>Terra</strong></li>
<li><strong>High Contrast</strong> (dark and light)</li>
</ul>
</li>
</ol>
<ol start="2">
<li>
<p><strong>Fine-Tuned Colors and Information Density</strong>: We have carefully considered feedback from our previous styles. The result is a balance of colors and information density designed to make our maps more easily consumable across different <strong>zoom levels</strong> and <strong>device profiles</strong>.</p>
</li>
<li>
<p><strong>Transport Features</strong>: We&rsquo;ve introduced transport features with <strong>hairline rendering</strong>. These features provide more context and information without compromising readability.</p>
</li>
<li>
<p><strong>Greener Base Map Colors</strong>: Our base map colors now support <strong>biomes</strong> and <strong>landcover classes</strong>, including subtle yet functional <strong>terrain shading</strong> rendered with <strong>ambient occlusion</strong>.</p>
</li>
</ol>
<ol start="5">
<li>
<p><strong>Improved Label Density</strong>: With this update, we have significantly improved label density. You&rsquo;ll find an even and consistent level of detail across various zoom levels and geographies.</p>
</li>
<li>
<p><strong>Clear Hierarchy of Types</strong>: We&rsquo;ve defined a clear hierarchy for labels, making it easy to distinguish between:</p>
<ul>
<li><strong>Transport and infrastructure labels</strong></li>
<li><strong>Administrative districts</strong></li>
<li><strong>Natural features</strong></li>
</ul>
</li>
<li>
<p><strong>Expanded Coverage</strong>: Thanks to our partners, the Microsoft mapping platform now combines and enhances data from various sources. As a result, we&rsquo;ve expanded our coverage in <strong>China</strong>, <strong>Japan</strong>, and <strong>Korea</strong>.</p>
</li>
</ol>
<ol start="8">
<li><strong>Additional Enhancements</strong>:
<ul>
<li><strong>Road Details</strong></li>
<li><strong>Building Footprints</strong></li>
<li><strong>Hiking Trail Coverage</strong></li>
<li><strong>Wider Zoom Level Ranges</strong> for the Terra style</li>
<li>More <strong>Transit Types and Information</strong> (e.g., ferry stops, metro stops, bus stops)</li>
<li>Additional details about <strong>mountains</strong>, <strong>altitude</strong>, and <strong>waterfall locations</strong></li>
</ul>
</li>
</ol>
<h2 id="exploring-the-new-style">Exploring the New Style</h2>
<p>If you want to explore the new style, visit our <a href="https://samples.azuremaps.com/" target="_blank" rel="noopener noreffer ">Azure Maps Samples</a> or try out the <a href="https://demo.azuremaps.com/" target="_blank" rel="noopener noreffer ">Azure Maps Demo site</a>.</p>
<p>Developers can also use the <strong>Azure Maps V3 Web Control</strong> in there applications by utilizing our <strong>CDN endpoint</strong> or the <strong><a href="https://www.npmjs.com/package/azure-maps-control" target="_blank" rel="noopener noreffer ">NPM package</a></strong>.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-javascript">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="o">&lt;</span><span class="nx">link</span> <span class="nx">rel</span><span class="o">=</span><span class="s2">&#34;stylesheet&#34;</span> <span class="nx">href</span><span class="o">=</span><span class="s2">&#34;https://atlas.microsoft.com/sdk/javascript/mapcontrol/3/atlas.min.css&#34;</span> <span class="nx">type</span><span class="o">=</span><span class="s2">&#34;text/css&#34;</span> <span class="o">/&gt;</span>
</span></span><span class="line"><span class="cl"><span class="o">&lt;</span><span class="nx">script</span> <span class="nx">src</span><span class="o">=</span><span class="s2">&#34;https://atlas.microsoft.com/sdk/javascript/mapcontrol/3/atlas.min.js&#34;</span><span class="o">&gt;&lt;</span><span class="err">/script&gt;</span></span></span></code></pre></div></div>
]]></description></item><item><title>Azure Maps Creator onboarding</title><link>https://clemens.ms/azure-maps-creator-onboarding/</link><pubDate>Mon, 27 Mar 2023 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/azure-maps-creator-onboarding/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><p><strong>Azure Maps Creator</strong> is a powerful product that transforms your static floor plans into dynamic, interactive indoor maps for your business locations. It allows you to overlay technical building plans ontop of Azure Maps, enabling the visualization of IoT data such as temperature, occupancy, and other location-based services. The onboarding process is streamlined, requiring only the preparation and uploading of your <strong>DWG floorplan</strong> files (the native file format for Autodesk&rsquo;s AutoCAD software) into <a href="https://aka.ms/azuremapscreator" target="_blank" rel="noopener noreffer ">Azure Maps Creator</a>.</p>
<blockquote>
<p><strong>Important:</strong> As of 2024, Azure Maps Creator has been deprecated and is no longer available. Microsoft has discontinued this service. If you&rsquo;re looking for indoor mapping solutions, please refer to the <a href="https://docs.microsoft.com/azure/azure-maps/" target="_blank" rel="noopener noreffer ">Azure Maps documentation</a> for current alternatives and migration guidance.</p></blockquote>
<h2 id="onboarding-simplified">Onboarding simplified</h2>
<p>The <a href="https://azure.github.io/azure-maps-creator-onboarding-tool/" target="_blank" rel="noopener noreffer ">Azure Maps Creator onboarding tool</a> has been designed to make the process of integrating DWG floorplans straightforward. With the simplified <a href="https://aka.ms/creator-drawingpackagerequirement" target="_blank" rel="noopener noreffer ">drawing package requirements</a>, the tool facilitates the development of indoor maps without the need for expensive third-party services or the risk of exposing sensitive data. The onboarding steps are clearly outlined in the  documentation.</p>
<h2 id="steps-to-onboard-your-indoor-map">Steps to onboard your indoor map</h2>
<ol>
<li><strong>Assign each DWG file to a facility level:</strong> This step organizes the levels within your facility, enabling seamless floor transitions and wayfinding capabilities in your indoor map.</li>
<li><strong>Map DWG layers that will be converted:</strong> Identify and map the DWG file layers that will be transformed into indoor map features, such as furniture or infrastructure components.</li>
<li><strong>Position Your Facility on the Map:</strong> Place your indoor map accurately over a base map to ensure proper visualization and alignment.</li>
</ol>
<p>After these steps, you&rsquo;ll have a drawing package ready for Azure Maps Creator. The final action is to upload this package, which will then allow you to utilize your custom indoor map.</p>
<h2 id="azure-maps-creator">Azure Maps Creator</h2>
<p>The simplified drawing package requirements and Azure Maps Creator onboarding tool are some of the many helpful additions we have made to Azure Maps Creator to offer an even better experience for our customers. It enables the transformation of static building floorplans into interactive experiences and smart spaces to enhance productivity, efficiency, and comfort. We are using it ourselves <a href="/how-microsoft-uses-azure-maps-creator" rel="">How Microsoft uses Azure Maps Creator</a> and are excited to share this technology with you to see how you will use it for indoor mapping scenarios.</p>
]]></description></item><item><title>How Microsoft uses Azure Maps Creator</title><link>https://clemens.ms/how-microsoft-uses-azure-maps-creator/</link><pubDate>Thu, 23 Mar 2023 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/how-microsoft-uses-azure-maps-creator/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><p>In the evolving landscape of remote work and <a href="https://www.microsoft.com/en-us/sustainability/approach" target="_blank" rel="noopener noreffer ">sustainability</a>, the optimization of physical spaces has become increasingly important. Microsoft is at the forefront of this transformation, utilizing <a href="https://aka.ms/azuremapscreator" target="_blank" rel="noopener noreffer ">Azure Maps Creator</a> to enhance the management of its diverse global workspaces. This product is essential for facilities managers, providing dynamic, interactive indoor mapping solutions that allow for real-time assessment of space and resources, quick response to critical issues, and seamless adaptation to the needs of a hybrid workforce—all while minimizing environmental impact.</p>
<blockquote>
<p><strong>Important:</strong> As of 2024, Azure Maps Creator has been deprecated and is no longer available. Microsoft has discontinued this service. If you&rsquo;re looking for indoor mapping solutions, please refer to the <a href="https://docs.microsoft.com/azure/azure-maps/" target="_blank" rel="noopener noreffer ">Azure Maps documentation</a> for current alternatives and migration guidance.</p></blockquote>
<p>Azure Maps Creator is not just a product for efficient space management; it&rsquo;s a showcase of Microsoft&rsquo;s commitment to creating exceptional experiences for employees, customers, and partners visiting its buildings. By integrating IoT sensors throughout its facilities, Microsoft can monitor various environmental parameters such as temperature, energy consumption, and occupancy levels. This data, coupled with the capabilities of <a href="https://learn.microsoft.com/en-us/azure/digital-twins/overview" target="_blank" rel="noopener noreffer ">Azure Digital Twins</a> and <a href="https://azuremaps.com/" target="_blank" rel="noopener noreffer ">Azure Maps</a>, empowers the <a href="https://www.microsoft.com/insidetrack/blog/transforming-microsoft-buildings-with-iot-technology-and-indoor-mapping/" target="_blank" rel="noopener noreffer ">Digital Workplace team</a> to construct detailed floorplans and data pipelines that support sustainable operations and superior indoor navigation.</p>
<p>Imagine a hybrid employee contemplating their commute to the office. With Azure Maps Creator, they can evaluate whether to attend a meeting in person or remotely via Microsoft Teams, consider the environmental and time implications of different travel options, and even check parking availability. This decision-making process is part of what Microsoft refers to as the &ldquo;Green Commute&rdquo; initiative, which aims to identify the most efficient commuting method in terms of both time and energy.</p>
<p>Upon arrival at a Microsoft facility, employees are greeted by kiosks equipped with Azure Maps Creator&rsquo;s interactive maps. These maps not only display detailed floor plans but also provide navigational guidance to meeting locations, individuals, or specific areas within the building. For the health-conscious, the maps can suggest stair routes as an alternative to elevators. And for those unexpected meetings, this experinace enables the instant search and reservation of available meeting rooms, accessible via the kiosk or a mobile device.</p>
<p>The integration of Azure Maps Creator into Microsoft&rsquo;s kiosks exemplifies the company&rsquo;s innovative approach to enhancing the workplace experience. It transforms technical building plans into smart, adaptable overlays that offer a wealth of interior data visualization options. To learn more about Azure Maps Creator and its comprehensive capabilities, be sure to explore the <a href="https://blog.azuremaps.com" target="_blank" rel="noopener noreffer ">Azure Maps Blog</a>.</p>
]]></description></item><item><title>Lowering Your Energy Bill: How Combining Dynamic Pricing and Home Battery Storage Can Save You Money</title><link>https://clemens.ms/energy-bill/</link><pubDate>Mon, 23 Jan 2023 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/energy-bill/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><h2 id="introduction">Introduction</h2>
<p>In today&rsquo;s energy and climate crisis, prices for energy such as natural gas for heating and electricity have risen significantly in recent years, with prices at least two to three times higher than in past decades. This is particularly true in Europe, where affordable natural gas from Russia is currently not available. The price of electricity is also closely linked to the price of natural gas, so even when clean electricity is generated through wind, solar, and nuclear power, the price is still higher than what we are used to. This is due to the demand for and availability of electricity, which is typically high during the day and in the evening, but low during nighttime. When there is no wind or sun, electricity is generated by gas, coal, and lignite power plants, which is harmful to the environment. The mix of energy and the linked gas price drives up our energy bills.</p>
<p>The demand for energy is increasing worldwide as countries and markets modernize and raise their standard of living. As a result, it is unlikely that energy prices will return to their previous lows. However, there are ways for consumers to reduce their energy bills.</p>
<h2 id="dynamic-pricing">Dynamic Pricing</h2>
<p>In the past, most people in the Netherlands had fixed energy contracts for one or more years, with a fixed monthly price. New energy contracts still use this model, but there is little incentive to save or reduce consumption and habits when demand is high. However, dynamic pricing in combination with home or block storage of electricity in batteries can make a big difference. By storing electricity when it is cheap (typically during the night) and using it during the day, you can save money without changing your habits.</p>
<h2 id="home-battery-storage">Home Battery Storage</h2>
<p>For example, I have a dynamic energy contract (<a href="https://www.anwb.nl/huis/energie" target="_blank" rel="noopener noreffer ">ANWB</a>) where the price of electricity changes every hour. I also have a home battery, a Tesla Powerwall 2, with a capacity of 13.5 KWh (read <a href="/my-solar-energy-and-tesla-powerwall-2-setup/" rel="">here</a> more details about my setup). I charge the battery automatically between 1am and 5am in the night when the price for electricity is almost free and sell my solar electricity during the day when the price is high. This has resulted in a very low energy bill for me.</p>
<p>Even if you do not have solar panels, a combination of a dynamic energy contract and a home battery can make a big difference in your energy bill. Home batteries are becoming more affordable each year, and in a normal setup, you can earn back your investment in just under two years with current energy prices.</p>
]]></description></item><item><title>Storing and querying your geospatial data in Azure</title><link>https://clemens.ms/storing-and-querying-your-geospatial-data-in-azure/</link><pubDate>Fri, 20 Jan 2023 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/storing-and-querying-your-geospatial-data-in-azure/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><p>While <a href="https://azuremaps.com/" target="_blank" rel="noopener noreffer ">Azure Maps</a> is known for great use cases around visualizing and interacting with a map and location data, you probably also need secure and reliable storage for that data that offers the flexibility to query your (location) data. In this blog post, we explore the different options for storing and querying geospatial data in Azure, including Azure Cosmos DB, Azure SQL Database, and Azure Blob Storage. Storing and querying geospatial data in Azure is a powerful and flexible way to manage and analyze large sets of geographic information.</p>
<h2 id="azure-cosmos-db">Azure Cosmos DB</h2>
<p><a href="https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/query/geospatial-intro" target="_blank" rel="noopener noreffer ">Azure Cosmos DB</a> is a globally distributed, multi-model database that supports document, key-value, graph, and column-family data models. One of the key features of Cosmos DB is its support for geospatial data, which allows you to store and query data in the form of points, lines, and polygons. Cosmos DB also supports spatial indexing and advanced querying capabilities, making it a great choice for applications that require real-time, low-latency access to geospatial data.</p>
<p>Example query:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-sql">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="n">f</span><span class="p">.</span><span class="n">id</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">FROM</span><span class="w"> </span><span class="n">Families</span><span class="w"> </span><span class="n">f</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">WHERE</span><span class="w"> </span><span class="n">ST_DISTANCE</span><span class="p">(</span><span class="n">f</span><span class="p">.</span><span class="k">location</span><span class="p">,</span><span class="w"> </span><span class="err">{</span><span class="s2">&#34;type&#34;</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Point&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;coordinates&#34;</span><span class="p">:[</span><span class="mi">31</span><span class="p">.</span><span class="mi">9</span><span class="p">,</span><span class="w"> </span><span class="o">-</span><span class="mi">4</span><span class="p">.</span><span class="mi">8</span><span class="p">]</span><span class="err">}</span><span class="p">)</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="mi">30000</span></span></span></code></pre></div></div>
<blockquote>
<p>Read here more information about <a href="https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/query/geospatial-intro" target="_blank" rel="noopener noreffer ">Geospatial and GeoJSON location data in Azure Cosmos DB</a>.</p></blockquote>
<h2 id="azure-sql-database">Azure SQL Database</h2>
<p>Another option for storing and querying geospatial data in Azure is <a href="https://learn.microsoft.com/en-us/sql/relational-databases/spatial/spatial-data-sql-server" target="_blank" rel="noopener noreffer ">Azure SQL Database</a>. SQL Database is a fully managed, relational database service that supports the spatial data types and functions of SQL Server. This allows you to store and query geospatial data using standard SQL syntax, and also includes spatial indexing and querying capabilities. SQL Database is a good choice for applications that require a traditional relational database model and support for SQL-based querying.</p>
<blockquote>
<p>Read here more information about <a href="https://learn.microsoft.com/en-us/sql/relational-databases/spatial/spatial-data-sql-server" target="_blank" rel="noopener noreffer ">Spatial Data in Azure SQL Database</a>.</p></blockquote>
<h2 id="azure-blob-storage">Azure Blob Storage</h2>
<p><a href="https://learn.microsoft.com/en-us/azure/storage/blobs/" target="_blank" rel="noopener noreffer ">Azure Blob Storage</a> can be used to store and query large amounts of unstructured data, including geospatial data. Blob Storage allows you to store data in the form of blobs, which can be accessed via a URL. This makes it a great option for storing large files, such as satellite imagery or shapefiles. While Blob Storage does not include built-in support for spatial querying, it can be used in conjunction with other Azure services, such as Azure Data Lake Storage or Azure Databricks, to perform spatial analysis on the data.</p>
<p>In this sample we used satellite imagery that is stored in Azure Blob storage</p>
<h2 id="geospatial-data-processing-and-analytics">Geospatial data processing and analytics</h2>
<p>To see a sample that pulls Azure Maps and Azure Databases together, see the Microsoft Learn topic <a href="https://learn.microsoft.com/en-us/azure/architecture/example-scenario/data/geospatial-data-processing-analytics-azure" target="_blank" rel="noopener noreffer ">Geospatial data processing and analytics</a>. This example scenarios also uses:</p>
<ul>
<li><a href="https://azure.microsoft.com/services/postgresql/" target="_blank" rel="noopener noreffer ">Azure Database for PostgreSQL</a> - a fully managed relational database service that&rsquo;s based on the community edition of the open-source PostgreSQL database engine.</li>
<li><a href="https://www.postgis.net/" target="_blank" rel="noopener noreffer ">PostGIS</a> - an extension for the PostgreSQL database that integrates with GIS servers. PostGIS can run SQL location queries that involve geographic objects.</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>Azure offers a variety of options for storing and querying geospatial data, including <strong>Azure Cosmos DB</strong>, <strong>Azure SQL Database</strong>, and <strong>Azure Blob Storage</strong>. Each of these services has its own set of features and capabilities, and choosing the right one will depend on the specific needs of your application. Whether you need low-latency access to real-time data, support for traditional SQL-based querying, or the ability to store and analyze large amounts of unstructured data, Azure has the tools you need to get the job done.</p>
]]></description></item><item><title>Azure Maps REST SDKs</title><link>https://clemens.ms/azure-maps-rest-sdks/</link><pubDate>Wed, 02 Nov 2022 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/azure-maps-rest-sdks/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><p>Azure Maps is more than just a Map on your website. It is a complete enterprise solution for location-aware solutions. For example, you can do (reverse) geocoding of customer addresses and use an isochrone to find out withs customers a close to your store or get weather conditions for all your past sales data to know withs products sell best by rain or hot weather or get the correct time-zone for your customer by translating an IP-address to a location and get the time-zone information, or you need to know what the travel time is between two or more locations. So many scenarios and use cases you can make location aware with Azure Maps.</p>
<p>You can call the <a href="https://learn.microsoft.com/en-us/rest/api/maps/" target="_blank" rel="noopener noreffer ">Azure Maps REST APIs</a> directly from any programming language, which is not difficult but always needs extra work. With the introduction of the public preview Azure Maps REST SDKs for <a href="https://github.com/Azure/azure-sdk-for-net/tree/main/sdk/maps" target="_blank" rel="noopener noreffer ">C#</a> (.NET), <a href="https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/maps" target="_blank" rel="noopener noreffer ">Java</a>, <a href="https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/maps" target="_blank" rel="noopener noreffer ">Phyton</a>, and <a href="https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/maps" target="_blank" rel="noopener noreffer ">TypeScript</a> (Node.js), you can earlier use the power of Azure Maps in your backend without the hassle of calling the APIs the correct way.</p>
<p>To give you a simple example in C#, we are searching for a Starbucks close to a customer&rsquo;s location in Seattle. Before we can begin, you need an Azure Maps key; see here <a href="https://aka.ms/AzureMapsGettingStarted" target="_blank" rel="noopener noreffer ">how to get a free Azure Maps key</a>.</p>
<p>The following code snippet creates a console program MapsDemo with .NET 8.0. You can use any <a href="https://dotnet.microsoft.com/en-us/platform/dotnet-standard#versions" target="_blank" rel="noopener noreffer ">.NET standard 2.0-compatible</a> version as the framework.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-powershell">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="n">dotnet</span> <span class="n">add</span> <span class="n">package</span> <span class="n">Azure</span><span class="p">.</span><span class="py">Maps</span><span class="p">.</span><span class="py">Search</span> <span class="p">-</span><span class="n">-prerelease</span></span></span></code></pre></div></div>
<p>The following code snippet demonstrates how, in a simple console application, to import the <code>Azure.Maps.Search</code> package and perform a fuzzy search on “Starbucks” near Seattle. In the <code>Program.cs</code> file add the following code:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-csharp">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">Azure</span><span class="p">;</span> 
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">Azure.Core.GeoJson</span><span class="p">;</span> 
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">Azure.Maps.Search</span><span class="p">;</span> 
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">Azure.Maps.Search.Models</span><span class="p">;</span> 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Use Azure Maps subscription key authentication </span>
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">credential</span> <span class="p">=</span> <span class="k">new</span> <span class="n">AzureKeyCredential</span><span class="p">(</span><span class="s">&#34;Azure_Maps_Subscription_key&#34;</span><span class="p">);</span> 
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">client</span> <span class="p">=</span> <span class="k">new</span> <span class="n">MapsSearchClient</span><span class="p">(</span><span class="n">credential</span><span class="p">);</span> 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">SearchAddressResult</span> <span class="n">searchResult</span> <span class="p">=</span> <span class="n">client</span><span class="p">.</span><span class="n">FuzzySearch</span><span class="p">(</span> 
</span></span><span class="line"><span class="cl">    <span class="s">&#34;Starbucks&#34;</span><span class="p">,</span> <span class="k">new</span> <span class="n">FuzzySearchOptions</span> 
</span></span><span class="line"><span class="cl">    <span class="p">{</span> 
</span></span><span class="line"><span class="cl">        <span class="n">Coordinates</span> <span class="p">=</span> <span class="k">new</span> <span class="n">GeoPosition</span><span class="p">(-</span><span class="m">122.31</span><span class="p">,</span> <span class="m">47.61</span><span class="p">),</span> 
</span></span><span class="line"><span class="cl">        <span class="n">Language</span> <span class="p">=</span> <span class="n">SearchLanguage</span><span class="p">.</span><span class="n">EnglishUsa</span> 
</span></span><span class="line"><span class="cl">    <span class="p">});</span> 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Print the search results </span>
</span></span><span class="line"><span class="cl"><span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">result</span> <span class="k">in</span> <span class="n">searchResult</span><span class="p">.</span><span class="n">Results</span><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="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;&#34;&#34; 
</span></span></span><span class="line"><span class="cl"><span class="s">        * {result.PointOfInterest.Name} 
</span></span></span><span class="line"><span class="cl"><span class="s">          {result.Address.StreetNumber} {result.Address.StreetName} 
</span></span></span><span class="line"><span class="cl"><span class="s">          {result.Address.Municipality} {result.Address.CountryCode} {result.Address.PostalCode} 
</span></span></span><span class="line"><span class="cl"><span class="s">          Coordinate: ({result.Position.Latitude:F4}, {result.Position.Longitude:F4}) 
</span></span></span><span class="line"><span class="cl"><span class="s">        &#34;&#34;&#34;</span><span class="p">);</span> 
</span></span><span class="line"><span class="cl"><span class="p">}</span> </span></span></code></pre></div></div>
<p>In the above code snippet, you create a <code>MapsSearchClient</code> object using your Azure credentials, then use that Search Client&rsquo;s <a href="https://learn.microsoft.com/en-us/dotnet/api/azure.maps.search.mapssearchclient.fuzzysearch" target="_blank" rel="noopener noreffer ">FuzzySearch</a> method passing in the point of interest (POI) name &ldquo;Starbucks&rdquo; and coordinates <code>GeoPosition(-122.31, 47.61)</code>. This all gets wrapped up by the SDK and sent to the Azure Maps REST endpoints. When the search results are returned, they&rsquo;re written out to the screen.</p>
<p>To run your application, go to the project folder and execute <code>dotnet run</code> in PowerShell.</p>
<p>More information you can read in the <a href="https://learn.microsoft.com/en-us/azure/azure-maps/rest-sdk-developer-guide" target="_blank" rel="noopener noreffer ">Azure Maps REST SDK Developer Guide</a>. Happy coding!</p>
]]></description></item><item><title>Add a custom WebGL layer to Azure Maps</title><link>https://clemens.ms/add-a-custom-webgl-layer-to-azure-maps/</link><pubDate>Tue, 11 Oct 2022 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/add-a-custom-webgl-layer-to-azure-maps/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><p>Enhancing your <strong>Azure Maps</strong> with a custom <strong>WebGL layer</strong> opens up a realm of possibilities for rendering dynamic 2D and 3D data. While Azure Maps provides a robust set of built-in features, there are times when you may require a more tailored solution. This is where the power of a custom WebGL layer shines.</p>
<p><a href="https://www.khronos.org/webgl/" target="_blank" rel="noopener noreffer ">WebGL</a>, a cross-platform and royalty-free web standard, empowers you to harness low-level 3D graphics right in your web browser. By utilizing WebGL, Azure Maps gains a performance edge, surpassing the capabilities of standard HTML canvas rendering. However, it&rsquo;s important to note that WebGL&rsquo;s low-level nature adds complexity and may not always align with straightforward business solutions.</p>
<p>To bridge this gap, Azure Maps&rsquo; custom WebGL layer can be integrated with leading open-source 3D frameworks such as <a href="https://www.babylonjs.com/" target="_blank" rel="noopener noreffer ">Babylon.js</a>, <a href="https://deck.gl/" target="_blank" rel="noopener noreffer ">Deck.gl</a>, and <a href="https://threejs.org/" target="_blank" rel="noopener noreffer ">Three.js</a>. These frameworks simplify the management of 2D and 3D layers, making it more accessible to create high-performance, interactive graphics that come to life in real-time—ideal for simulations, data visualizations, animations, and 3D modeling.</p>
<p>To implement a custom WebGL layer, you&rsquo;ll utilize the <code>map.layer.add()</code> function, passing in a new <code>WebGLLayer</code>. This layer requires a renderer object, which you&rsquo;ll define by implementing the <code>WebGLRenderer</code> interface. You can also specify additional options, such as visibility at certain zoom levels:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-javascript">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="c1">// Add the layer to the map with layer options.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">map</span><span class="p">.</span><span class="nx">layers</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="k">new</span> <span class="nx">atlas</span><span class="p">.</span><span class="nx">layer</span><span class="p">.</span><span class="nx">WebGLLayer</span><span class="p">(</span><span class="s2">&#34;layerId&#34;</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">renderer</span><span class="o">:</span> <span class="nx">myRenderer</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">minZoom</span><span class="o">:</span> <span class="mi">10</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">maxZoom</span><span class="o">:</span> <span class="mi">22</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">visible</span><span class="o">:</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl"><span class="p">}));</span></span></span></code></pre></div></div>
<p>Azure Maps operates on the Spherical Mercator projection (EPSG: 3857), a system that converts the spherical shape of the globe into a 2D map, stretching it at the poles for a square representation. The map&rsquo;s camera matrix translates these Spherical Mercator coordinates into WebGL coordinates for your custom layer.</p>
<h2 id="3d-frameworks">3D Frameworks</h2>
<ul>
<li><strong>Babylon.js</strong>: A comprehensive, real-time 3D engine developed by Microsoft, offering a user-friendly framework for game and rendering engine capabilities.</li>
<li><strong>Deck.gl</strong>: A WebGL-powered framework designed for the visual exploratory analysis of large datasets, allowing for the construction of intricate visualizations through layer composition.</li>
<li><strong>Three.js</strong>: A versatile, lightweight 3D library that&rsquo;s easy to use across different web browsers.</li>
</ul>
<p>For those eager to dive into developing their custom WebGL layer, extensive <a href="https://learn.microsoft.com/en-us/azure/azure-maps/webgl-custom-layer" target="_blank" rel="noopener noreffer ">documentation</a> is available, along with a plethora of <a href="https://samples.azuremaps.com/" target="_blank" rel="noopener noreffer ">samples</a> showcasing the vast potential of Azure Maps. Explore these resources to kickstart your journey and unlock the full creative power of Azure Maps with custom WebGL layers.</p>
]]></description></item><item><title>Azure Maps Web Application Authentication</title><link>https://clemens.ms/azure-maps-authentication/</link><pubDate>Mon, 15 Aug 2022 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/azure-maps-authentication/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><h2 id="introduction">Introduction</h2>
<p>One of the requirements when building a business application, which may give access to private business data, is that only authenticated employees or agents be able to see that data. So how can you use <a href="https://azuremaps.com/" target="_blank" rel="noopener noreffer ">Azure Maps</a> in combination with authentication and authorization to ensure only the people that should be allowed have access?</p>
<p>Our <a href="https://docs.azuremaps.com/" target="_blank" rel="noopener noreffer ">Azure Maps docs</a> describe in detail <a href="https://docs.microsoft.com/azure/azure-maps/azure-maps-authentication" target="_blank" rel="noopener noreffer ">many different authentication scenarios</a> but the complexity can make it seem difficult to implement. This blog post will focus on our most requested authentication scenario for Azure Maps. Use the following step by step guidance to have a .NET web application embedded Azure Maps web control where only authenticated users can see the website and use the map.</p>
<blockquote>
<p>🚀 <strong>Updated Resources Available!</strong> For the latest version of this tutorial with updated code samples, improved best practices, and additional security enhancements, check out the companion GitHub repository: <strong><a href="https://github.com/cschotte/azure-maps-authentication" target="_blank" rel="noopener noreffer ">azure-maps-authentication</a></strong>. The repository includes modernized .NET code, enhanced Azure CLI scripts, and additional authentication patterns not covered in this original post.</p></blockquote>
<h2 id="prerequisites">Prerequisites</h2>
<p>In this article, we use the following resources:</p>
<ul>
<li>.NET 8.0 and the C# programming language. You can download, and install the latest version of .NET from <a href="https://dot.net/" target="_blank" rel="noopener noreffer ">https://dot.net/</a></li>
<li>To make it easier to edit source code, we also recommend installing Visual Studio Code Edition, which is a lightweight but powerful source code editor from Microsoft <a href="https://code.visualstudio.com/" target="_blank" rel="noopener noreffer ">https://code.visualstudio.com/</a></li>
<li>Before you can use Azure Maps, you will need to sign up for a free Azure subscription, at <a href="https://azure.microsoft.com/free" target="_blank" rel="noopener noreffer ">https://azure.microsoft.com/free</a></li>
<li>And finally, install the Azure Command-Line Interface (CLI) tools. Read here <a href="https://docs.microsoft.com/cli/azure/install-azure-cli" target="_blank" rel="noopener noreffer ">How to install the Azure CLI</a>.</li>
</ul>
<h2 id="step-1-basic-web-application-with-azure-maps">Step 1. Basic Web Application with Azure Maps</h2>
<p>Let&rsquo;s start with a basic .NET web application and Azure Maps. No authentication yet, that will come in the next paragraph. This first step will use an Azure Maps Key (a ‘shared Key authentication’ or subscription key) that should <strong>not</strong> be used in production. An Azure Maps Key has complete control over your Azure Maps resource. In the next paragraph, we will remove this key and replace this with managed identities for Azure resources.</p>
<p>Create a folder, we called ours <code>AzureMapsDemo</code>, and add a new web application to it. Then open the newly created web application in Visual Studio Code. Start PowerShell (or any other terminal) and enter the following commands:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-cmd">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl"><span class="k">mkdir</span> AzureMapsDemo
</span></span><span class="line"><span class="cl"><span class="k">cd</span> .\AzureMapsDemo
</span></span><span class="line"><span class="cl">dotnet new mvc
</span></span><span class="line"><span class="cl">code .</span></span></code></pre></div></div>
<p>Next, we need to add the Azure Maps web control to the Home view, open the file <code>Views/Home/index.cshtml</code>, and replace all the content with:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-html">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl">@{
</span></span><span class="line"><span class="cl">    ViewData[&#34;Title&#34;] = &#34;Home Page&#34;;
</span></span><span class="line"><span class="cl">}
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;text-center&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">h1</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;display-4&#34;</span><span class="p">&gt;</span>Azure Maps<span class="p">&lt;/</span><span class="nt">h1</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span>Learn about <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;https://docs.microsoft.com/azure/azure-maps/&#34;</span><span class="p">&gt;</span>building Azure Maps apps with ASP.NET Core<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>.<span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;myMap&#34;</span> <span class="na">style</span><span class="o">=</span><span class="s">&#34;width:100%;min-width:290px;height:600px;&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">@section Scripts
</span></span><span class="line"><span class="cl">{
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;stylesheet&#34;</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;https://atlas.microsoft.com/sdk/javascript/mapcontrol/3/atlas.min.css&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;https://atlas.microsoft.com/sdk/javascript/mapcontrol/3/atlas.min.js&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="kd">var</span> <span class="nx">map</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// Initialize a map instance.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nx">map</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">atlas</span><span class="p">.</span><span class="nx">Map</span><span class="p">(</span><span class="s1">&#39;myMap&#39;</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">center</span><span class="o">:</span> <span class="p">[</span><span class="o">-</span><span class="mf">122.33</span><span class="p">,</span> <span class="mf">47.6</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="nx">zoom</span><span class="o">:</span> <span class="mi">12</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nx">style</span><span class="o">:</span> <span class="s1">&#39;satellite_road_labels&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nx">view</span><span class="o">:</span> <span class="s1">&#39;Auto&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="c1">// Add authentication details for connecting to Azure Maps.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="nx">authOptions</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nx">authType</span><span class="o">:</span> <span class="s1">&#39;subscriptionKey&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="nx">subscriptionKey</span><span class="o">:</span> <span class="s1">&#39;[YOUR_AZURE_MAPS_KEY]&#39;</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></span><span class="line"><span class="cl">        <span class="c1">// Wait until the map resources are ready.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nx">map</span><span class="p">.</span><span class="nx">events</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="s1">&#39;ready&#39;</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// Add your post map load code here.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="p">});</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div>
<p>As you can see, we need a subscription key for Azure Maps before starting the web application and using the map. In the next step, we are creating an Azure resource group and adding a new Azure Maps Account. Then we extract the Azure Maps Primary Key from this Azure Maps Account, which we use in our Home view.</p>
<p>1.1 Login into your Azure subscription and save the Azure subscription Id, we need this for later.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-cmd">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">az login</span></span></code></pre></div></div>
<p>1.2 (Optional) Select the subscription where you would like to create the Azure Maps Account.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-cmd">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">az account set --subscription <span class="s2">&#34;&lt;your subscription&gt;&#34;</span></span></span></code></pre></div></div>
<p>1.3 Create a resource group, and change the name and the location for your needs.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-cmd">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">az group create -l westeurope -n rg-azuremaps</span></span></code></pre></div></div>
<p>1.4 Create the Azure Maps Account, and accept the terms and conditions. Save the uniqueId for later.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-cmd">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">az maps account create -n map-azuremaps -g rg-azuremaps -s <span class="s2">&#34;G2&#34;</span> --kind <span class="s2">&#34;Gen2&#34;</span></span></span></code></pre></div></div>
<p>1.5 Now we can extract the Azure Maps Primary Key and add it to the Home view in our web application.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-cmd">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">az maps account keys list -n map-azuremaps -g rg-azuremaps</span></span></code></pre></div></div>
<p>1.6 Replace the <code>[YOUR_AZURE_MAPS_KEY]</code> in the file <code>Views/Home/index.cshtml</code> with the Azure Maps Primary Key we just listed in step 1.5.</p>
<p>1.7 Now we can run and test our AzureMapsDemo web application.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-cmd">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">dotnet run</span></span></code></pre></div></div>
<h2 id="step-2-managed-identities-for-azure-maps">Step 2. Managed identities for Azure Maps</h2>
<p>In this paragraph, we are removing the ‘shared Key authentication’ (the Azure Maps subscription key) and replacing this with a more secure and production ready managed identities for Azure Maps.</p>
<blockquote>
<p>Managed identities for Azure resources provide Azure services with an automatically managed application-based security principal that can authenticate with Azure AD. With Azure role-based access control (Azure RBAC), the managed identity security principal can be authorized to access Azure Maps services.</p></blockquote>
<p>This means that the web application can request a short-lived token to get access to Azure Maps from Azure Active Directory (AAD). Because this is managed, we do not need to know any passwords or create users. However, to get this token back to the client (the Azure Maps Web Controls runs in the users’ browser), we need to create a simple token proxy API in our web application to forward this token.</p>
<p>We start by creating an Azure Web App where our web application will be hosted and running. This Azure Web App then needs to have rights to get a token for Azure Maps, which we will forward using the token proxy API we create in the below steps.</p>
<p>2.1 Create an app service plan and web app, and <strong>change the unique name</strong> <code>web-azuremaps</code> and the location for your needs.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-cmd">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">az appservice plan create -g rg-azuremaps -n plan-azuremaps -l westeurope
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">az webapp create -g rg-azuremaps -p plan-azuremaps -n web-azuremaps -r <span class="s2">&#34;dotnet:8&#34;</span></span></span></code></pre></div></div>
<p>2.2 Next, we create a system-assigned identity for this web app. When finished, we are presented with the <code>principalId</code>, we need this in the next step. To make it simple, you can see the system-assigned identity as an account Azure manages.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-cmd">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">az webapp identity assign -n web-azuremaps -g rg-azuremaps</span></span></code></pre></div></div>
<p>2.3 Now that we have the <code>principalId</code> (use this in the below command) for this system-assigned identity, we can assign the role (what can this system-assigned identity do and access). In this step, we assign the role of <a href="https://docs.microsoft.com/azure/azure-maps/azure-maps-authentication#picking-a-role-definition" target="_blank" rel="noopener noreffer ">Azure Maps Data Reader</a> to this system-assigned identity, which means that this system-assigned identity can only read and not modify or delete data from your Azure Maps account. You already see this is way more secure than the plain Azure Maps key, which has all the rights to do everything. We also need the <code>[YOUR_AZURE_SUBSCRIPTION_ID]</code> from the first step.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-cmd">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">az role assignment create --assignee <span class="s2">&#34;[PRINCIPAL_ID]&#34;</span> --role <span class="s2">&#34;Azure Maps Data Reader&#34;</span> --scope <span class="s2">&#34;/subscriptions/[YOUR_AZURE_SUBSCRIPTION_ID]/resourceGroups/rg-azuremaps/providers/Microsoft.Maps/accounts/map-azuremaps&#34;</span></span></span></code></pre></div></div>
<blockquote>
<p><strong>Hint</strong> to get your Azure subscription Id use the following command: <code>az account subscription list</code></p></blockquote>
<p>2.4 To get the access token from Azure Active Directory (AAD) back to the client (the web browser), we will create a simple proxy API forwarding this access token. We start by creating an API controller in our web application and adding the <code>GetAzureMapsToken()</code> method.</p>
<p>2.5 First, we must add the <strong>Azure Identity</strong> NuGet package to our web application.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-cmd">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">dotnet add package Azure.Identity</span></span></code></pre></div></div>
<p>2.6 Next, we create a new <code>ApiController.cs</code> file under the folder <strong>Controllers</strong>. This new <code>ApiController.cs</code> file will have a method <code>GetAzureMapsToken()</code> that is acting like a proxy for our access token. Read <a href="https://docs.microsoft.com/aspnet/core/tutorials/first-mvc-app/adding-controller" target="_blank" rel="noopener noreffer ">here</a> more about Controllers in a MVC web application.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-csharp">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">Azure.Core</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">Azure.Identity</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">Microsoft.AspNetCore.Mvc</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">namespace</span> <span class="nn">AzureMapsDemo.Controllers</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">ApiController</span> <span class="p">:</span> <span class="n">Controller</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">static</span> <span class="k">readonly</span> <span class="n">DefaultAzureCredential</span> <span class="n">tokenProvider</span> <span class="p">=</span> <span class="k">new</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">IActionResult</span><span class="p">&gt;</span> <span class="n">GetAzureMapsToken</span><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="kt">var</span> <span class="n">accessToken</span> <span class="p">=</span> <span class="k">await</span> <span class="n">tokenProvider</span><span class="p">.</span><span class="n">GetTokenAsync</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="k">new</span> <span class="n">TokenRequestContext</span><span class="p">(</span><span class="k">new</span><span class="p">[]</span> <span class="p">{</span> <span class="s">&#34;https://atlas.microsoft.com/.default&#34;</span> <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></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="k">new</span> <span class="n">OkObjectResult</span><span class="p">(</span><span class="n">accessToken</span><span class="p">.</span><span class="n">Token</span><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></code></pre></div></div>
<p>2.7 Now that we have our token API proxy, we only need to change the authentication options for the Azure Maps Web Control. <strong>Replace</strong> in the file <code>Views/Home/index.cshtml</code> the authOptions with the following:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-js">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="c1">// Add authentication details for connecting to Azure Maps.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">authOptions</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Use Azure Active Directory authentication.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">authType</span><span class="o">:</span> <span class="s1">&#39;anonymous&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Your Azure Maps client id for accessing your Azure Maps account.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">clientId</span><span class="o">:</span> <span class="s1">&#39;[YOUR_AZUREMAPS_CLIENT_ID]&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">getToken</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</span><span class="p">,</span> <span class="nx">map</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// URL to your authentication service that retrieves
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="c1">// an Azure Active Directory Token.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="kd">var</span> <span class="nx">tokenServiceUrl</span> <span class="o">=</span> <span class="s2">&#34;/api/GetAzureMapsToken&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="nx">fetch</span><span class="p">(</span><span class="nx">tokenServiceUrl</span><span class="p">).</span><span class="nx">then</span><span class="p">(</span><span class="nx">r</span> <span class="p">=&gt;</span> <span class="nx">r</span><span class="p">.</span><span class="nx">text</span><span class="p">()).</span><span class="nx">then</span><span class="p">(</span><span class="nx">token</span> <span class="p">=&gt;</span> <span class="nx">resolve</span><span class="p">(</span><span class="nx">token</span><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></code></pre></div></div>
<p>2.8 We also need to update the <code>clientId</code> we saved when we created the Azure Maps account. (Optional) To get the Azure Maps Client Id again, use the value of <code>uniqueId</code> from:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-cmd">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">az maps account show -n map-azuremaps -g rg-azuremaps</span></span></code></pre></div></div>
<p>2.9 Now we can build and deploy our web application that uses managed identities for Azure Maps. We first build and create a release package.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-cmd">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">dotnet publish --configuration Release
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Compress-Archive -Path bin\Release\net8.0\publish\* -DestinationPath release1.zip</span></span></code></pre></div></div>
<p>2.10 Then we publish our release package to the Azure Web App.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-cmd">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">az webapp deployment source config-zip -g rg-azuremaps -n web-azuremaps --src release1.zip</span></span></code></pre></div></div>
<p>2.11 Open a web browser and navigate to the <a href="https://web-azuremaps.azurewebsites.net/" target="_blank" rel="noopener noreffer ">https://web-azuremaps.azurewebsites.net/</a> where the <strong>web-azuremaps</strong> subdomain is your unique name when creating the Azure Web App. The application looks like this:</p>
<p>2.12.	(Optional) We can also navigate to the token proxy API <a href="https://web-azuremaps.azurewebsites.net/api/GetAzureMapsToken" target="_blank" rel="noopener noreffer ">https://web-azuremaps.azurewebsites.net/api/GetAzureMapsToken</a>, copy the token, and past this in the <a href="https://jwt.ms/" target="_blank" rel="noopener noreffer ">https://jwt.ms/</a> tool to decode and inspect the token.</p>
<h2 id="step-3-protecting-the-web-application-and-the-azure-maps-token-proxy-api">Step 3. Protecting the web application and the Azure Maps token proxy API</h2>
<p>The web application we built in the last paragraph uses managed identities, and the Azure Maps Web Control uses the access token. Unfortunately, the web application and token proxy API are still accessible to everybody. Therefore, in this paragraph, we are adding the Azure Active Directory (AAD) Authentication to the web application and the token proxy API, so that only authenticated users can view the web application and use the Azure Maps Web Control in a secure way.</p>
<p>3.1 We start by registering an application in the Azure Active Directory, and we need this application registration later to give access to the web application and token proxy API.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-cmd">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">az ad app create --display-name <span class="s2">&#34;Azure Maps Demo App&#34;</span> --web-redirect-uris https://web-azuremaps.azurewebsites.net/signin-oidc --enable-access-token-issuance true --enable-id-token-issuance true --sign-in-audience AzureADMyOrg</span></span></code></pre></div></div>
<p>3.2 We need to add four Identity and Authentication NuGet packages to our web application.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-cmd">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">dotnet add package Microsoft.Identity.Web
</span></span><span class="line"><span class="cl">dotnet add package Microsoft.Identity.Web.UI
</span></span><span class="line"><span class="cl">dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
</span></span><span class="line"><span class="cl">dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect</span></span></code></pre></div></div>
<p>3.3 Next, we need to add the <code>[Authorize]</code> attribute to every controller in our web application. Below is our token API proxy controller as an example. Do not forget to do this also for the Home controller!</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-csharp">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">Azure.Core</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">Azure.Identity</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">Microsoft.AspNetCore.Mvc</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">Microsoft.AspNetCore.Authorization</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">namespace</span> <span class="nn">AzureMapsDemo.Controllers</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="na">
</span></span></span><span class="line"><span class="cl"><span class="na">[Authorize]</span>
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">ApiController</span> <span class="p">:</span> <span class="n">Controller</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></div></div>
<p>3.4 In the program startup file <code>Program.cs</code> we need to add the Authentication and Authentication logic. Replace all the default code in the <code>Program.cs</code> file with the following:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-csharp">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">Microsoft.AspNetCore.Authentication</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">Microsoft.AspNetCore.Authentication.OpenIdConnect</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">Microsoft.AspNetCore.Authorization</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">Microsoft.AspNetCore.Mvc.Authorization</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">Microsoft.Identity.Web</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">Microsoft.Identity.Web.UI</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">builder</span> <span class="p">=</span> <span class="n">WebApplication</span><span class="p">.</span><span class="n">CreateBuilder</span><span class="p">(</span><span class="n">args</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Add services to the container.</span>
</span></span><span class="line"><span class="cl"><span class="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="n">AddAuthentication</span><span class="p">(</span><span class="n">OpenIdConnectDefaults</span><span class="p">.</span><span class="n">AuthenticationScheme</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">AddMicrosoftIdentityWebApp</span><span class="p">(</span><span class="n">builder</span><span class="p">.</span><span class="n">Configuration</span><span class="p">.</span><span class="n">GetSection</span><span class="p">(</span><span class="s">&#34;AzureAd&#34;</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="n">AddAuthorization</span><span class="p">(</span><span class="n">options</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">options</span><span class="p">.</span><span class="n">FallbackPolicy</span> <span class="p">=</span> <span class="n">options</span><span class="p">.</span><span class="n">DefaultPolicy</span><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></span><span class="line"><span class="cl"><span class="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="n">AddControllersWithViews</span><span class="p">(</span><span class="n">options</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">policy</span> <span class="p">=</span> <span class="k">new</span> <span class="n">AuthorizationPolicyBuilder</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="n">RequireAuthenticatedUser</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="n">Build</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="n">options</span><span class="p">.</span><span class="n">Filters</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="k">new</span> <span class="n">AuthorizeFilter</span><span class="p">(</span><span class="n">policy</span><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="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="n">AddRazorPages</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">AddMicrosoftIdentityUI</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">app</span> <span class="p">=</span> <span class="n">builder</span><span class="p">.</span><span class="n">Build</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Configure the HTTP request pipeline.</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(!</span><span class="n">app</span><span class="p">.</span><span class="n">Environment</span><span class="p">.</span><span class="n">IsDevelopment</span><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="n">app</span><span class="p">.</span><span class="n">UseExceptionHandler</span><span class="p">(</span><span class="s">&#34;/Home/Error&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.</span>
</span></span><span class="line"><span class="cl">    <span class="n">app</span><span class="p">.</span><span class="n">UseHsts</span><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></span><span class="line"><span class="cl"><span class="n">app</span><span class="p">.</span><span class="n">UseHttpsRedirection</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="n">app</span><span class="p">.</span><span class="n">UseStaticFiles</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app</span><span class="p">.</span><span class="n">UseRouting</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app</span><span class="p">.</span><span class="n">UseAuthentication</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="n">app</span><span class="p">.</span><span class="n">UseAuthorization</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app</span><span class="p">.</span><span class="n">MapControllerRoute</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">name</span><span class="p">:</span> <span class="s">&#34;default&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">pattern</span><span class="p">:</span> <span class="s">&#34;{controller=Home}/{action=Index}/{id?}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="n">app</span><span class="p">.</span><span class="n">MapRazorPages</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="n">app</span><span class="p">.</span><span class="n">MapControllers</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app</span><span class="p">.</span><span class="n">Run</span><span class="p">();</span></span></span></code></pre></div></div>
<p>3.5 The last step before redeploying our secure web application is to add the details from our registered application in the Azure Active Directory into the configuration file. Open the <code>appsettings.json</code> file and replace this with:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-json">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><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;AzureAd&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;Instance&#34;</span><span class="p">:</span> <span class="s2">&#34;https://login.microsoftonline.com/&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;Domain&#34;</span><span class="p">:</span> <span class="s2">&#34;[PUBLISHER_DOMAIN]&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;TenantId&#34;</span><span class="p">:</span> <span class="s2">&#34;[AAD_TENANT_ID]&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;ClientId&#34;</span><span class="p">:</span> <span class="s2">&#34;[APP_ID]&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;CallbackPath&#34;</span><span class="p">:</span> <span class="s2">&#34;/signin-oidc&#34;</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;Logging&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;LogLevel&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;Default&#34;</span><span class="p">:</span> <span class="s2">&#34;Information&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;Microsoft.AspNetCore&#34;</span><span class="p">:</span> <span class="s2">&#34;Warning&#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="nt">&#34;AllowedHosts&#34;</span><span class="p">:</span> <span class="s2">&#34;*&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p>3.6 Replace the <code>[PUBLISHER_DOMAIN]</code> and <code>[APP_ID]</code> with the values we saved in step 1 when we registered the application. Your Azure Active Directory Tenant ID <code>[AAD_TENANT_ID]</code>, you can get with the following command:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-cmd">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">az account tenant list</span></span></code></pre></div></div>
<p>3.7 Now we can build and deploy our web application that uses Azure Active Directory to login. We first build and create a release package.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-cmd">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">dotnet publish --configuration Release
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Compress-Archive -Path bin\Release\net8.0\publish\* -DestinationPath release2.zip</span></span></code></pre></div></div>
<p>3.8 Then we publish our release package to the Azure Web App.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-cmd">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">az webapp deployment source config-zip -g rg-azuremaps -n web-azuremaps --src release2.zip</span></span></code></pre></div></div>
<p>3.9 Open a web browser and navigate to the <a href="https://web-azuremaps.azurewebsites.net/" target="_blank" rel="noopener noreffer ">https://web-azuremaps.azurewebsites.net/</a> where the <strong>web-azuremaps</strong> subdomain is your unique name when creating the Azure Web App. You are now prompted to log in with your work or school account (AAD) and give permissions.</p>
<p>3.10 A recommended last step is to disable the use of the Azure Maps Key authentication.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-cmd">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">az maps account update -n map-azuremaps -g rg-azuremaps --disable-local-auth true -s <span class="s2">&#34;G2&#34;</span></span></span></code></pre></div></div>
<h2 id="conclusion">Conclusion</h2>
<p>When we have done all the steps in this step-by-step article, you have a protected web application in combination with Azure Maps that uses of Azure Active Directory, Azure role-based access control (<a href="https://docs.microsoft.com/azure/role-based-access-control/overview" target="_blank" rel="noopener noreffer ">Azure RBAC</a>), and <a href="https://docs.microsoft.com/azure/azure-maps/azure-maps-authentication" target="_blank" rel="noopener noreffer ">Azure Maps tokens</a>. I recommend that you read our <a href="https://docs.microsoft.com/azure/azure-maps/authentication-best-practices" target="_blank" rel="noopener noreffer ">Authentication best practices</a> and Azure Maps documentation. Also the <a href="https://samples.azuremaps.com/" target="_blank" rel="noopener noreffer ">Azure Maps Samples</a> website offers so great ideas, with source code on Github, and uses most of the steps described in this article. Happy coding!</p>
<blockquote>
<p>This blog post was initially written by me for the <a href="https://blog.azuremaps.com" target="_blank" rel="noopener noreffer ">Azure Maps Tech Blog</a>.</p></blockquote>
]]></description></item><item><title>Protecting and Hiding your Bing Maps Key</title><link>https://clemens.ms/bing-maps-key/</link><pubDate>Tue, 05 Apr 2022 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/bing-maps-key/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><h2 id="introduction">Introduction</h2>
<p>When using <a href="https://www.microsoft.com/maps" target="_blank" rel="noopener noreffer ">Bing Maps for Enterprise</a> in your solution/application, you need a <strong>Basic Key</strong> (limited free trial) or an <strong>Enterprise key</strong> to use the services. For example, you would add a Bing Maps Key to the script URL loading the Bing Maps Web Control like this:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-html">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;https://www.bing.com/api/maps/mapcontrol?callback=GetMap&amp;key={your bing maps key}&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span></span></span></code></pre></div></div>
<blockquote>
<p><strong>Important:</strong> Bing Maps for Enterprise has been deprecated and is no longer available. Microsoft has discontinued this service. If you&rsquo;re looking for mapping solutions, please refer to the <a href="https://docs.microsoft.com/azure/azure-maps/" target="_blank" rel="noopener noreffer ">Azure Maps documentation</a> for current alternatives and migration guidance.</p></blockquote>
<p>Now your key is open text on your site source code and people who look can find and use your key. Search engines will index your page and, as a result, will also store your key. Is this a problem? Not really.</p>
<h2 id="protecting">Protecting</h2>
<p>The Bing Maps key is mainly used to determine the usage and allow access to Bing Maps features. To protect your Bing Maps key, so it can&rsquo;t be misused on other websites, there is an option in the <a href="https://www.bingmapsportal.com/" target="_blank" rel="noopener noreffer ">Bing Maps Dev Center</a> to protect your key. This security option allows you to specify a list of referrers (website URLs) and IP numbers who can use your key. When at least one referrer rule is active, any requests that omit a referrer and any requests from non-approved referrers will be blocked, preventing others from using your key for requests. You can have up to 300 referrer and IP security rules per key.</p>
<p>Your key is now protected but is still visible in your website code. So how do I hide my Bing Maps key?</p>
<blockquote>
<p>A best practice is <strong>never to store any keys or certificates in source code</strong>.</p></blockquote>
<h2 id="hiding">Hiding</h2>
<p>To hide the Bing Maps key, you create a simple API endpoint that will only return the Bing Maps key if the request comes from a trusted referral URL. The <a href="https://samples.bingmapsportal.com/" target="_blank" rel="noopener noreffer ">Bing Maps Samples</a> site is a good example that uses this approach.</p>
<p>In this example we are using an Anonymous HttpTrigger <a href="https://azure.microsoft.com/en-us/services/functions/" target="_blank" rel="noopener noreffer ">Azure Function</a> written in C# that returns the Bing Maps key:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-csharp">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">static</span> <span class="k">class</span> <span class="nc">GetBingMapsKey</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">static</span> <span class="k">readonly</span> <span class="kt">string</span><span class="p">[]</span> <span class="n">allowd</span> <span class="p">=</span> <span class="p">{</span> <span class="s">&#34;https://samples.bingmapsportal.com/&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                                <span class="s">&#34;http://localhost&#34;</span><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="na">
</span></span></span><span class="line"><span class="cl"><span class="na">    [FunctionName(&#34;GetBingMapsKey&#34;)]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">static</span> <span class="n">IActionResult</span> <span class="n">Run</span><span class="p">([</span><span class="n">HttpTrigger</span><span class="p">(</span><span class="n">AuthorizationLevel</span><span class="p">.</span><span class="n">Anonymous</span><span class="p">,</span> <span class="s">&#34;get&#34;</span><span class="p">,</span> <span class="n">Route</span> <span class="p">=</span> <span class="kc">null</span><span class="p">)]</span> <span class="n">HttpRequest</span> <span class="n">req</span><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="kt">string</span> <span class="n">referer</span> <span class="p">=</span> <span class="n">req</span><span class="p">.</span><span class="n">Headers</span><span class="p">[</span><span class="s">&#34;Referer&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="kt">string</span><span class="p">.</span><span class="n">IsNullOrEmpty</span><span class="p">(</span><span class="n">referer</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="k">new</span> <span class="n">UnauthorizedResult</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kt">string</span> <span class="n">result</span> <span class="p">=</span> <span class="n">Array</span><span class="p">.</span><span class="n">Find</span><span class="p">(</span><span class="n">allowd</span><span class="p">,</span> <span class="n">site</span> <span class="p">=&gt;</span> <span class="n">referer</span><span class="p">.</span><span class="n">StartsWith</span><span class="p">(</span><span class="n">site</span><span class="p">,</span> <span class="n">StringComparison</span><span class="p">.</span><span class="n">OrdinalIgnoreCase</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="kt">string</span><span class="p">.</span><span class="n">IsNullOrEmpty</span><span class="p">(</span><span class="n">result</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="k">new</span> <span class="n">UnauthorizedResult</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// Get your Bing Maps key from https://www.bingmapsportal.com/</span>
</span></span><span class="line"><span class="cl">        <span class="kt">string</span> <span class="n">key</span> <span class="p">=</span> <span class="n">Environment</span><span class="p">.</span><span class="n">GetEnvironmentVariable</span><span class="p">(</span><span class="s">&#34;BING_MAPS_SUBSCRIPTION_KEY&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="k">new</span> <span class="n">OkObjectResult</span><span class="p">(</span><span class="n">key</span><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></code></pre></div></div>
<p>The Bing Maps key is stored server-side in this Azure Function Application settings field. We are using the <code>GetEnvironmentVariable()</code> to get the key.</p>
<p>Next, we need to load the Bing Maps script and get the key from the API client-side. Finally, we use the following code snippet to load Bing Maps dynamically:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-html">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Dynamic load the Bing Maps Key and Script
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="c1">// Get your own Bing Maps key at https://www.microsoft.com/maps
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="p">(</span><span class="kr">async</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kd">let</span> <span class="nx">script</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s2">&#34;script&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="kd">let</span> <span class="nx">bingKey</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="s2">&#34;https://samples.azuremaps.com/api/GetBingMapsKey&#34;</span><span class="p">).</span><span class="nx">then</span><span class="p">(</span><span class="nx">r</span> <span class="p">=&gt;</span> <span class="nx">r</span><span class="p">.</span><span class="nx">text</span><span class="p">()).</span><span class="nx">then</span><span class="p">(</span><span class="nx">key</span> <span class="p">=&gt;</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">key</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">        <span class="nx">script</span><span class="p">.</span><span class="nx">setAttribute</span><span class="p">(</span><span class="s2">&#34;src&#34;</span><span class="p">,</span> <span class="sb">`https://www.bing.com/api/maps/mapcontrol?callback=GetMap&amp;key=</span><span class="si">${</span><span class="nx">bingKey</span><span class="si">}</span><span class="sb">`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">script</span><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">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span></span></span></code></pre></div></div>
<p>The browser will run this code and create at runtime in the DOM the same line of <code>&lt;script&gt;</code> tag we have seen at the beginning of this blog post to load Bing Maps and the Key. An additional advantage is that the Bing Maps key is not stored in the source code anymore and that you can use <a href="/infrastructure-as-code" rel="">IaC</a> and build pipelines to deploy the solution.</p>
<blockquote>
<p><strong>Note:</strong> Only hiding the Bing Maps key alone is not enough as a security measure. We recommend you still enable the security option in the Bing Maps Dev Center!</p></blockquote>
]]></description></item><item><title>Azure Maps Power BI update</title><link>https://clemens.ms/azure-maps-power-bi-update/</link><pubDate>Tue, 22 Mar 2022 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/azure-maps-power-bi-update/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><p>The <strong>Azure Maps Power BI Visual</strong> provides a rich set of data visualizations to enhance your data with location context. In the March release of Power BI, the Azure Maps visual introduces two new tools: <strong>Geocoding capabilities</strong> and a <strong>Pie Chart layer</strong>.</p>
<h2 id="geocoding-in-power-bi">Geocoding in Power BI</h2>
<p>When dealing with data that has a location context, such as addresses or other geographic information, you might lack the precise point location (latitude-longitude) needed to plot these addresses on a map. The new geocode capabilities in the Azure Maps Power BI visual allow you to convert address data into location data directly within Power BI. The Azure Maps geocoder is flexible and can work with incomplete address information or spelling mistakes. Additionally, it supports regional geocoding for various levels, including country, state or province, city, county, postal code, and partial address data.</p>
<h3 id="the-location-field">The Location Field</h3>
<p>In the <strong>Location field</strong>, you can add multiple data items. The more data you include, the better context the geocoder has, resulting in a more precise point. For example, the term &ldquo;London&rdquo; could refer to either &ldquo;London, England&rdquo; or &ldquo;London, Ontario, Canada.&rdquo; By providing additional data, you improve the geocoder&rsquo;s ability to disambiguate requests and deliver better location results.</p>
<h2 id="adding-the-pie-chart-layer">Adding the Pie Chart Layer</h2>
<p>Following the geocoder, the <strong>pie chart layer</strong> allows you to place pie charts at specific locations relevant to the data they represent. The pie chart visually displays numerical proportions for a given location. For instance:</p>
<ul>
<li>Understand the market share of different products in a region.</li>
<li>Analyze the distribution of voters across districts.</li>
<li>Identify which product category sells best in specific store locations.</li>
</ul>
<p>In this month&rsquo;s release, you can now transform a <strong>bubble layer</strong> into a pie chart layer, enabling you to visualize data using this new representation.</p>
<h3 id="rendering-a-pie-chart">Rendering a Pie Chart</h3>
<p>To create a pie chart, follow these steps:</p>
<ol>
<li>Drag categorical data into the <strong>legend field</strong>.</li>
<li>Add numeric data related to your map.</li>
<li>The categorical data determines the number of pie slices, while the numeric data defines the proportion of each slice.</li>
</ol>
<h2 id="learn-more">Learn More</h2>
<p>For detailed instructions on these new features, explore our <strong>Power BI Azure Maps &ldquo;How To&rdquo; guides</strong>:</p>
<ul>
<li><a href="https://docs.microsoft.com/en-us/azure/azure-maps/power-bi-visual-geocode" target="_blank" rel="noopener noreffer ">Geocoder feature</a></li>
<li><a href="https://docs.microsoft.com/en-us/azure/azure-maps/power-bi-visual-add-pie-chart-layer" target="_blank" rel="noopener noreffer ">Pie chart layer</a></li>
</ul>
]]></description></item><item><title>Azure Maps Weather Services adds three new services</title><link>https://clemens.ms/azure-maps-weather-services-adds-three-new-services/</link><pubDate>Thu, 27 Jan 2022 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/azure-maps-weather-services-adds-three-new-services/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><p><a href="https://docs.microsoft.com/rest/api/maps/weather" target="_blank" rel="noopener noreffer ">Azure Maps Weather Services</a>, which became generally available in <strong>April 2021</strong>, has recently expanded its offerings with three new services: <strong>Historical Weather</strong>, <strong>Air Quality</strong>, and <strong>Tropical Storms</strong>. These additions empower developers and companies to enhance their capabilities when it comes to weather data.</p>
<h2 id="historical-weather">Historical Weather</h2>
<p>The <strong>Historical Weather API</strong> provides actuals, normals, and records climatology data by day for a specified date range, up to <strong>31 days</strong> in a single API request. Depending on the location and feature, historical data may be available as far back as <strong>5 to 40+ years</strong>. The information includes:</p>
<ul>
<li>Temperatures</li>
<li>Precipitation</li>
<li>Snowfall</li>
<li>Snow depth</li>
<li>Cooling/heating degree day information</li>
</ul>
<p>Historical Weather data enables customers to analyze past climatology information and make predictions about future weather conditions. It is valuable for various use cases, including forensics, weather studies, and business analytics. For instance, businesses can determine how past weather impacted logistics, sales, and other scenarios. By incorporating historical weather data into sales records, companies can even predict which products will sell better under specific weather conditions. Additionally, it helps optimize decisions such as changing to winter tires at the right time based on historical climate patterns.</p>
<h2 id="air-quality">Air Quality</h2>
<p>The <strong>Air Quality API</strong> provides detailed information about current and forecasted air pollutants and air quality concentration. Forecasted data is available by the hour (upcoming 1, 12, 24, 48, 72, and 96 hours) and by day (upcoming 1 to 7 days). The information includes:</p>
<ul>
<li>Pollution levels</li>
<li>Air quality index values</li>
<li>Dominant pollutants</li>
<li>Brief statements summarizing risk levels and suggested precautions</li>
</ul>
<p>If you’re developing an outdoor running or cycling app, you can use Air Quality information to warn users about air quality conditions in the next hour. Additionally, individuals can track the impact of air quality on their health. This data is relevant for various scenarios, including outdoor sensors, commuting, exercising, and indoor use with HVAC systems and air purifiers.</p>
<h2 id="tropical-storms">Tropical Storms</h2>
<p>The <strong>Tropical Storms API</strong> provides information on government-issued active and forecasted tropical storms. It includes details such as:</p>
<ul>
<li>Location coordinates</li>
<li>Forecast</li>
<li>Creation date</li>
<li>Status</li>
<li>Geometry</li>
<li>Basin ID</li>
<li>Window</li>
<li>Wind speed</li>
<li>Wind radii</li>
</ul>
<p>Tropical storms, also known as hurricanes, cyclones, or typhoons depending on the region, can have significant impacts. Researchers, businesses, and public safety agencies can use this data for observational and forecast purposes. Whether monitoring current storms or researching past tropical storm events, the Tropical Storms API provides valuable insights.</p>
<p>All the <a href="https://docs.microsoft.com/rest/api/maps/weather/" target="_blank" rel="noopener noreffer ">Azure Maps Weather APIs</a> can be found in the documenation.</p>
]]></description></item><item><title>Managed Identities for Azure Maps</title><link>https://clemens.ms/managed-identities-for-azure-maps/</link><pubDate>Sun, 31 Oct 2021 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/managed-identities-for-azure-maps/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><p>In many enterprise organizations, there are strict processes for privacy, access, and handling of personally identifiable information (PII). Azure Maps is a global Azure service, which means it is available worldwide (except for China and South Korea), but it also needs to store metadata and logs somewhere. In addition, Azure Maps Creator is an addon for private indoor maps that also holds map data. So, where do we keep this data?</p>
<p>Even when Azure Maps stores almost no information, you still need to select in which region the metadata, logs, and private maps must be stored. When creating a new Azure Maps account, the region selection only affects your Azure Maps account&rsquo;s account management and metadata capabilities. Basically, you decide that all metadata, logs, and private map data stay in the selected region (the United States or Europe). Microsoft doesn&rsquo;t control or limit the locations from which your end users come to Azure Maps.</p>
<p>Azure Maps shares customer-provided address/location queries with third-party TomTom for mapping functionality purposes. These queries aren&rsquo;t linked to any customer or end-user when shared with TomTom and can&rsquo;t be used to identify individuals. TomTom is listed on Microsoft&rsquo;s <a href="https://servicetrust.microsoft.com/Search?keyword=Subprocessors%20List" target="_blank" rel="noopener noreffer ">Online Services Subcontractor List</a>.</p>
<h2 id="azure-maps-keys">Azure Maps Keys</h2>
<p>When using Azure Maps Keys, you can access, modify, upload, and delete all the data associated with that Azure Maps account. Not the best choice for a production solution. I recommend using Azure Managed identities for Azure Maps, which give you several powerful security benefits and access controls. You can disable (also by policy) the use of Azure Maps Keys for development, testing, and production. The best thing about managed identities is that you do not need to store any secrets, and you can no longer accidentally leak any keys in your source code.</p>
<h2 id="setting-up-managed-identities">Setting up managed identities</h2>
<p>You can set up managed identities easily using the Azure Portal (what we are doing here), or if you like, you can also use the Azure command line (CLI). Here, I have written a <a href="/azure-maps-authentication/" rel="">step-by-step guide on using managed identities with Azure Maps and .NET</a>.</p>
<p>Before we start, you need an <strong>Azure Account</strong>, a <strong>Web App</strong> and an <strong>Azure Maps</strong> instance.</p>
<p>I start here with a Web App, where you like to enable managed identities for. First, navigate to your Web App, select the Identity option, and enable the system-assigned identity. Next, click on the permissions button.</p>
<p>Azure manages this system-assigned identity; no need to know any secrets. Next, we need to assign what this managed identity can do and access it or not. Finally, click the plus button to add what role this managed identity needs.</p>
<p>Select the resource group where you have created your Azure Maps account. We can use many different roles, but we are only interested in the Azure Maps roles for this blog. Type Azure Maps to filter and select the role you need for your solution. More roles are available here.</p>
<table>
  <thead>
      <tr>
          <th>Azure Role Definition</th>
          <th>Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Azure Maps Contributor</td>
          <td>Grants access <strong>all</strong> Azure Maps resource management.</td>
      </tr>
      <tr>
          <td>Azure Maps Data Contributor</td>
          <td>Grants access to <strong>read</strong>, <strong>write</strong>, and <strong>delete</strong> access to map related data from an Azure maps account.</td>
      </tr>
      <tr>
          <td>Azure Maps Data Reader</td>
          <td>Grants access to <strong>read</strong> map related data from an Azure maps account</td>
      </tr>
      <tr>
          <td>Azure Maps Search and Render Data Reader</td>
          <td>Grants access to <strong>limited</strong> set of data APIs for common visual web scenarios. Specifically, <strong>render</strong> and <strong>search</strong> data APIs</td>
      </tr>
  </tbody>
</table>
<p>Now we have enabled managed identities in our Web App and allowed it to access Azure Maps with the selected role. In the Web App, we can now create a token proxy that uses the managed identity to generate an Azure Maps token that we use in our client app. Example code in C#:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-csharp">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">Azure.Core</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">Azure.Identity</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">Microsoft.AspNetCore.Mvc</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">namespace</span> <span class="nn">AzureMapsDemo.Controllers</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">ApiController</span> <span class="p">:</span> <span class="n">Controller</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">static</span> <span class="k">readonly</span> <span class="n">DefaultAzureCredential</span> <span class="n">tokenProvider</span> <span class="p">=</span> <span class="k">new</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">IActionResult</span><span class="p">&gt;</span> <span class="n">GetAzureMapsToken</span><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="kt">var</span> <span class="n">accessToken</span> <span class="p">=</span> <span class="k">await</span> <span class="n">tokenProvider</span><span class="p">.</span><span class="n">GetTokenAsync</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="k">new</span> <span class="n">TokenRequestContext</span><span class="p">(</span><span class="k">new</span><span class="p">[]</span> <span class="p">{</span> <span class="s">&#34;https://atlas.microsoft.com/.default&#34;</span> <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></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="k">new</span> <span class="n">OkObjectResult</span><span class="p">(</span><span class="n">accessToken</span><span class="p">.</span><span class="n">Token</span><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></code></pre></div></div>
<p>This Azure Maps token proxy can then be used in the Azure Maps Web Control, which runs in your browser by the following JavaScript code. Tokens are short-lived and are automatically renewed by the Azure Maps Web Control.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-javascript">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="c1">// Add authentication details for connecting to Azure Maps.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">authOptions</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Use Azure Active Directory authentication.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">authType</span><span class="o">:</span> <span class="s1">&#39;anonymous&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Your Azure Maps client id for accessing your Azure Maps account.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">clientId</span><span class="o">:</span> <span class="s1">&#39;[YOUR_AZUREMAPS_CLIENT_ID]&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">getToken</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</span><span class="p">,</span> <span class="nx">map</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// URL to your authentication service that retrieves
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="c1">// an Azure Active Directory Token.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="kd">var</span> <span class="nx">tokenServiceUrl</span> <span class="o">=</span> <span class="s2">&#34;/api/GetAzureMapsToken&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="nx">fetch</span><span class="p">(</span><span class="nx">tokenServiceUrl</span><span class="p">).</span><span class="nx">then</span><span class="p">(</span><span class="nx">r</span> <span class="p">=&gt;</span> <span class="nx">r</span><span class="p">.</span><span class="nx">text</span><span class="p">()).</span><span class="nx">then</span><span class="p">(</span><span class="nx">token</span> <span class="p">=&gt;</span> <span class="nx">resolve</span><span class="p">(</span><span class="nx">token</span><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></code></pre></div></div>
<p>I hope this blog helped you with setting up Managed Identities in the Azure Portal. If you need a step-by-step guide on how to use managed identities and authorization in a .NET web application, read my <a href="/azure-maps-authentication/" rel="">Azure Maps Web Application Authentication</a> blog.</p>
<blockquote>
<p>This blog post was initially written by me for the <a href="https://blog.azuremaps.com" target="_blank" rel="noopener noreffer ">Azure Maps Tech Blog</a>.</p></blockquote>
]]></description></item><item><title>Introducing the Heat Map Layer in Azure Maps Visual for Power BI</title><link>https://clemens.ms/introducing-the-heat-map-layer-in-azure-maps-visual-for-power-bi/</link><pubDate>Mon, 11 Oct 2021 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/introducing-the-heat-map-layer-in-azure-maps-visual-for-power-bi/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><p>We are thrilled to announce the addition of the <strong>Heat Map layer</strong> option to the <strong>Azure Maps Visual</strong> in <strong>Power BI</strong>. This powerful feature allows you to visualize data density using various colors, highlighting data “hot spots” on a map. Whether you’re analyzing customer behavior, regional performance, or statistical trends, the heat map provides valuable insights.</p>
<h2 id="key-benefits-of-the-heat-map-layer-in-power-bi">Key Benefits of the Heat Map Layer in Power BI</h2>
<ol>
<li><strong>Data Density Visualization:</strong> Heat maps are ideal for rendering datasets with a large number of points. They effectively display data concentration and distribution. Use heat maps to compare customer satisfaction rates, shop performance, or any other relevant metrics across different regions or countries.</li>
<li><strong>Frequency Analysis:</strong> For example, by measuring the frequency with which customers visit shopping malls in various locations, you can identify popular areas and potential growth opportunities.</li>
<li><strong>Statistical Insights:</strong> Heat maps are excellent for visualizing vast statistical and geographical datasets. Explore patterns, correlations, and outliers with ease.</li>
</ol>
<h2 id="customization-options">Customization Options</h2>
<p>The heatmap formatting pane (Format) empowers users to tailor their visualizations according to their preferences. Here are the customization options available:</p>
<ol>
<li><strong>Radius Configuration:</strong> Adjust the radius of each data point in either pixels or meters. Fine-tune the level of detail based on your specific use case.</li>
<li><strong>Opacity and Intensity:</strong> Customize the opacity and intensity of the heatmap layer. Find the right balance between visibility and aesthetics.</li>
<li><strong>Weighted Data Points:</strong> Specify whether the Size field should be used as the weight for each data point. This allows you to emphasize certain data elements.</li>
<li><strong>Color Customization:</strong> Choose custom colors using the color picker. Match the heatmap colors to your brand or convey specific meanings.</li>
<li><strong>Zoom Levels:</strong> Set the minimum and maximum zoom levels for the heatmap. Ensure optimal visibility at different map scales.</li>
<li><strong>Layer Arrangement:</strong> Arrange the heat map layer position relative to other layers (e.g., 3D bar chart, bubble layers). Create cohesive visualizations that tell a compelling story.</li>
</ol>
<p>With the new Heat Map layer, you can unlock deeper insights from your data and enhance your Power BI reports. Try it out today and discover the hidden patterns within your geographical data!</p>
]]></description></item><item><title>Use Azure Maps to calculate an isochrone to reach your customers</title><link>https://clemens.ms/isochrone/</link><pubDate>Tue, 21 Sep 2021 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/isochrone/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><p>Imagine you are a store owner and would like to target customers that live within a 15-minute drive from your store with advertising for your weekly specials. You could draw a circle on a map, guessing that is about 15 minutes away, but it will not truly represent the time it will take for customers to get to your store. For example, a customer living near a major transit route can live further away from the store than a customer living in a less well-served part of the city. To meet this need, an <strong>isochrone</strong> is a polygon (an area on a map) of expected travel time. It represents the locations that will take the specified time, or less, it will take to get to a specific point (your store, in this case). Estimating an isochrone correctly, including all the variables like traffic, road, and vehicle conditions, is very hard to do by yourself!</p>
<h2 id="azure-maps-get-route-range">Azure Maps Get Route Range</h2>
<p>Using the <a href="https://docs.microsoft.com/en-us/rest/api/maps/route/get-route-range" target="_blank" rel="noopener noreffer ">Get Route Range</a> API from Azure Maps makes it very easy to calculate the isochrone. For our store example, we need to calculate a 15-minute isochrone for every store we have. We only need the coordinates (longitude and latitude) from our stores and the drivetime in seconds (15 x 60 = 900 sec).</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-http">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-http" data-lang="http"><span class="line"><span class="cl"><span class="err">GET https://atlas.microsoft.com/route/range/json?subscription-key=[subscription-key]&amp;api-version=1.0&amp;query=47.65431,-122.1291891&amp;timeBudgetInSec=900</span></span></span></code></pre></div></div>
<h2 id="search-customer-address">Search Customer Address</h2>
<p>To determine which customer is living inside the 15-minute drive isochrone, we first need the coordinates for every customer&rsquo;s address. Then, we simply loop through all our customers and use the <a href="https://docs.microsoft.com/en-us/rest/api/maps/search/get-search-address" target="_blank" rel="noopener noreffer ">Get Search Address</a> API to get the coordinates.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-http">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-http" data-lang="http"><span class="line"><span class="cl"><span class="err">GET https://atlas.microsoft.com/search/address/json?subscription-key=[subscription-key]&amp;api-version=1.0&amp;query=15127 NE 24th Street, Redmond, WA 98052</span></span></span></code></pre></div></div>
<h2 id="point-in-polygon">Point In Polygon</h2>
<p>The last step is to check if a customer coordinate is inside one of our store isochrones. We use the <a href="https://docs.microsoft.com/en-us/rest/api/maps/spatial/post-point-in-polygon" target="_blank" rel="noopener noreffer ">Point In Polygon</a> API to determine if this is true.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-http">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-http" data-lang="http"><span class="line"><span class="cl"><span class="err">POST https://atlas.microsoft.com/spatial/pointInPolygon/json?subscription-key=[subscription-key]&amp;api-version=1.0&amp;lat=33.5362475&amp;lon=-111.9267386</span></span></span></code></pre></div></div>
<p>And the request body (the store isochrone)</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-json">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><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;type&#34;</span><span class="p">:</span> <span class="s2">&#34;FeatureCollection&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;features&#34;</span><span class="p">:</span> <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="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;Feature&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;properties&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;geometryId&#34;</span><span class="p">:</span> <span class="mi">1001</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;geometry&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;Polygon&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;coordinates&#34;</span><span class="p">:</span> <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="mf">-111.9267386</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="mf">33.5362475</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="mf">-111.9627875</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="mf">33.5104882</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="mf">-111.9027061</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="mf">33.5004686</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="mf">-111.9267386</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="mf">33.5362475</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></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></code></pre></div></div>
<p>We now can target those customers with store-specific offers and promotions.</p>
<h2 id="examples">Examples</h2>
<p>What schools are within a 20-minute walk from my home? What available jobs are within a 30-minute transit commute? Where you are located and what is near you brings context to the choice of where you want to live and Azure Maps Isochrone API can help you bring that location intelligence into your applications.</p>
<ul>
<li><a href="https://samples.azuremaps.com/?search=Isochrone" target="_blank" rel="noopener noreffer ">Azure Maps Isochrone Samples</a></li>
<li><a href="https://docs.microsoft.com/en-us/azure/azure-maps/how-to-use-best-practices-for-search" target="_blank" rel="noopener noreffer ">Best practices for Azure Maps Search Service</a></li>
<li><a href="https://docs.microsoft.com/en-us/azure/azure-maps/how-to-search-for-address" target="_blank" rel="noopener noreffer ">Search for a location using Azure Maps Search services</a></li>
</ul>
]]></description></item><item><title>Azure DevOps Dashboard</title><link>https://clemens.ms/azure-devops-dashboard/</link><pubDate>Tue, 15 Jun 2021 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/azure-devops-dashboard/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.png" referrerpolicy="no-referrer">
            </div><h1 id="introduction">Introduction</h1>
<p>When you are managing Azure DevOps in a large enterprise organization, and you are still using only one Azure DevOps organization account, you are probably hitting some limits or have potential performance issues. Microsoft&rsquo;s recommendation is to have around 300 projects in a single Azure DevOps organization account. I have seen Azure DevOps organizations with more than 600 projects that still work.</p>
<p>The solution is to set up a multi-organization structure. Move all the inactive projects to an archive or boneyard Azure DevOps organization account and add an extra Azure DevOps organization account per department.</p>
<p>Next, you need some insights and automation on which projects have no activity anymore. The Azure DevOps Dashboard gives you the basic insights and an API to automate tasks like emailing the owners of inactive projects.</p>
<h1 id="azure-devops-dashboard">Azure DevOps Dashboard</h1>
<p>This dashboard solution generates a simple overview of all the <a href="https://dev.azure.com/" target="_blank" rel="noopener noreffer ">Azure DevOps</a> projects in your organization and calculates the last known activity in <strong>days</strong> on commits, work items, and the project itself. You can connect this dashboard (using the included endpoint) to <a href="https://flow.microsoft.com/" target="_blank" rel="noopener noreffer ">Microsoft Power Automate</a> or Excel to automate tasks on project level.</p>
<h2 id="installation">Installation</h2>
<p>The solution runs on as a single <a href="https://azure.microsoft.com/en-us/services/app-service/web/" target="_blank" rel="noopener noreffer ">Azure Web App</a>, it uses a background <a href="https://docs.microsoft.com/en-us/azure/app-service/webjobs-create" target="_blank" rel="noopener noreffer ">WebJob</a> to collect all the data needed to present in the web dashboard.</p>
<h3 id="prerequisites">Prerequisites</h3>
<ol>
<li>An Azure account with an active subscription. <a href="https://azure.microsoft.com/free/dotnet" target="_blank" rel="noopener noreffer ">Create an account for free</a>.</li>
<li>Install the <a href="https://docs.microsoft.com/en-us/cli/azure/install-azure-cli-windows" target="_blank" rel="noopener noreffer ">Azure CLI on Windows</a> to automate the following steps</li>
<li>An Azure DevOps personal access token (PAT). See here <a href="https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&amp;tabs=preview-page" target="_blank" rel="noopener noreffer ">how to get a personal access token</a>.</li>
<li>Download the Azure DevOps Dashboard <a href="https://github.com/cschotte/Azure-DevOps-Dashboard/raw/main/Release.zip" target="_blank" rel="noopener noreffer ">Release.zip</a> package.</li>
</ol>
<h3 id="create-an-azure-web-app">Create an Azure Web App</h3>
<p>In the next steps, you will create a resource group, an app service plan (the webserver), and the Web App (the solution itself). We also add two application settings to store the Azure DevOps personal access token.</p>
<ol>
<li>Login into your Azure subscription</li>
</ol>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-cmd">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">az login</span></span></code></pre></div></div>
<ol start="2">
<li>(Optional) Select the subscription where you like to deploy the dashboard.</li>
</ol>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-cmd">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">az account set --subscription <span class="s2">&#34;&lt;your subscription&gt;&#34;</span></span></span></code></pre></div></div>
<ol start="3">
<li>Create a resource group, change the name <code>rg-azdevops</code></li>
</ol>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-cmd">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">az group create -l westeurope -n rg-azdevops</span></span></code></pre></div></div>
<ol start="4">
<li>Create an app service plan and webapp, change the names <code>plan-azdevops</code> and <code>azdevops</code></li>
</ol>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-cmd">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">az appservice plan create -g rg-azdevops -n plan-azdevops -l westeurope
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">az webapp create -g rg-azdevops -p plan-azdevops -n azdevops -r <span class="s2">&#34;dotnet:8&#34;</span></span></span></code></pre></div></div>
<ol start="5">
<li>Add your Azure DevOps URL and personal access token (PAT)</li>
</ol>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-cmd">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">az webapp config appsettings set -g rg-azdevops -n azdevops --settings azDevOpsPat=<span class="s2">&#34;&lt;your token&gt;&#34;</span>
</span></span><span class="line"><span class="cl">az webapp config appsettings set -g rg-azdevops -n azdevops --settings azDevOpsUri=<span class="s2">&#34;https://dev.azure.com/&lt;yourorgname&gt;&#34;</span></span></span></code></pre></div></div>
<ol start="6">
<li>Set the <code>always-on</code> future we need for the WebJob</li>
</ol>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-cmd">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">az webapp config set -g rg-azdevops -n azdevops --always-on true</span></span></code></pre></div></div>
<h3 id="deploy-the-azure-devops-dashboard">Deploy the Azure DevOps Dashboard</h3>
<p>Did you download the Azure DevOps Dashboard <a href="https://github.com/cschotte/Azure-DevOps-Dashboard/raw/main/Release.zip" target="_blank" rel="noopener noreffer ">Release.zip</a> package? After the installation we also run the WebJob for the first time, this can take a while depending on how many projects you have in your Azure DevOps organization account.</p>
<blockquote>
<p><strong>Authentication</strong> In the release package authentication is disabled! Please register your application first in your Azure Active Directory by following the steps described <a href="https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-v2-aspnet-core-webapp" target="_blank" rel="noopener noreffer ">here</a>. You only need to update the <strong>appsettings.json</strong> inside the release package.</p></blockquote>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-cmd">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">az webapp deployment source config-zip -g rg-azdevops -n azdevops --src Release.zip
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">az webapp webjob triggered run -n azdevops -g rg-azdevops --webjob-name Webjob</span></span></code></pre></div></div>
<h2 id="architecture">Architecture</h2>
<p>You can also run the WebJob locally, set the following two environment variable first <code>azDevOpsUri</code>
and <code>azDevOpsPat</code> that corresponds with your Azure DevOps organization account:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-cmd">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl"><span class="k">SET</span> <span class="nv">azDevOpsPat</span><span class="p">=</span>tjqp44k54nqfmppaqd7di27kpvh...........
</span></span><span class="line"><span class="cl"><span class="k">SET</span> <span class="nv">azDevOpsUri</span><span class="p">=</span>https://dev.azure.com/yourorgname.....</span></span></code></pre></div></div>
<h2 id="using-the-api">Using the API</h2>
<p>To automate tasks, you can use the API to connect to Excel, Microsoft Power Automate, or whatever you need. The <code>/api/data</code> API will return a list of the following project properties:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-json">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><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="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;projectId&#34;</span><span class="p">:</span> <span class="s2">&#34;guid&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;project name&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;description&#34;</span><span class="p">:</span> <span class="s2">&#34;project description&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;url&#34;</span><span class="p">:</span> <span class="s2">&#34;https://dev.azure.com/projectname&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;owners&#34;</span><span class="p">:</span> <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="nt">&#34;displayName&#34;</span><span class="p">:</span> <span class="s2">&#34;Contoso Admin name&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&#34;mailAddress&#34;</span><span class="p">:</span> <span class="s2">&#34;admin@contoso.com&#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="nt">&#34;processTemplate&#34;</span><span class="p">:</span> <span class="s2">&#34;Scrum&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;lastProjectUpdateTime&#34;</span><span class="p">:</span> <span class="s2">&#34;2021-03-22T11:40:32.09Z&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;lastCommitDate&#34;</span><span class="p">:</span> <span class="s2">&#34;2020-04-23T18:00:27Z&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;lastWorkItemDate&#34;</span><span class="p">:</span> <span class="s2">&#34;0001-01-01T00:00:00&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;lastKnownActivity&#34;</span><span class="p">:</span> <span class="s2">&#34;2021-03-22T11:40:32.09Z&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;projectAge&#34;</span><span class="p">:</span> <span class="mf">83.92575148777316</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></div></div>
<h2 id="source-code">Source Code</h2>
<p>Alle source code can be found on <a href="https://github.com/cschotte/Azure-DevOps-Dashboard" target="_blank" rel="noopener noreffer ">GitHub</a>.</p>
]]></description></item><item><title>Do I get wet feet? Draw a flood map using Azure Maps Elevation</title><link>https://clemens.ms/azure-maps-elevation/</link><pubDate>Mon, 14 Jun 2021 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/azure-maps-elevation/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><h2 id="introduction">Introduction</h2>
<p>When you are living in the Netherlands you are used to that, nearly 26% of its land falling below sea level, and about 50% is just only exceeding 1 m (3.3 ft) above. The Dutch people have lived many centuries battling the water, not only from the sea but also from her rivers. To protect the land the Dutch have built many sophisticated protecting- and management systems to handle the water, like the <a href="https://www.bing.com/search?q=Dutch%20Delta%20Works" target="_blank" rel="noopener noreffer ">Delta Works</a>. Building only a dike or dam is not enough. Today we have won, but we know that we cannot rust, climate change (heavy rain showers) and sea levels are rising globally. Do we (or you) get wet feet in the future?</p>
<blockquote>
<p><strong>Important</strong> The Azure Maps Elevation services and Render V2 DEM tiles have been retired and will no longer be available and supported after 5 May 2023. All other Azure Maps APIs, Services and TilesetIDs are unaffected by this retirement. For more details, see <a href="https://azure.microsoft.com/en-us/updates/azure-maps-elevation-apis-and-render-v2-dem-tiles-will-be-retired-on-5-may-2023/" target="_blank" rel="noopener noreffer ">Elevation Services Retirement</a></p></blockquote>
<p>To start, we need to know which areas of land are almost below sea level so that we can plan and take additional actions. To make this visible we can use <a href="https://azure.microsoft.com/en-us/services/azure-maps/" target="_blank" rel="noopener noreffer ">Azure Maps</a> and use the <a href="https://azure.microsoft.com/en-us/updates/azure-maps-elevation-service-is-now-generally-available/" target="_blank" rel="noopener noreffer ">Azure Maps Elevation Service</a> to make a basic flood map. The Azure Maps Elevation Service provides pole-to-pole coverage with &lt;4M absolute and &lt;2m relative accuracy. The elevation data represents a digital terrain model (DTM), man-made entities (e.g., buildings) are artificially flattened, and elevation is measured to the ground surface.</p>
<h2 id="what-do-we-need">What do we need?</h2>
<p>To get started you need a free <a href="https://azure.microsoft.com/en-us/free/" target="_blank" rel="noopener noreffer ">Azure subscription</a> and an Azure Maps account. This provides us access to the Elevation API that we need to build the flood map. The Elevation API gives elevation back in meters for coordinates, with WGS84 longitude and latitude, on a map. We can use a Bounding Box, Points, and a Polyline as input. In the below picture you see an aerial photo of the harbor and city of Rotterdam. The flood map shows in blue all the land that is below sea level and is vulnerable, and in green land that is high enough.</p>
<h2 id="azure-maps">Azure Maps</h2>
<p>We only need a single HTML file, that references the Azure Maps <em>Maps Control</em>, <em>Map Drawing Tools</em> libraries, and some basic HTML and JavaScript.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-html">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="c">&lt;!-- Add references to the Azure Maps Map control JavaScript and CSS files. --&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;stylesheet&#34;</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;https://atlas.microsoft.com/sdk/javascript/mapcontrol/2/atlas.min.css&#34;</span><span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;https://atlas.microsoft.com/sdk/javascript/mapcontrol/2/atlas.min.js&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">&lt;!-- Add references to the Azure Maps Map Drawing Tools JavaScript and CSS files. --&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;stylesheet&#34;</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;https://atlas.microsoft.com/sdk/javascript/drawing/0/atlas-drawing.min.css&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;https://atlas.microsoft.com/sdk/javascript/drawing/0/atlas-drawing.min.js&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span></span></span></code></pre></div></div>
<p>We need to initialize the map control and add additional layers to it to draw on, like the controls and the floodmap itself. First we initialize the map control:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-js">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="c1">// Initialize a map instance.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">map</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">atlas</span><span class="p">.</span><span class="nx">Map</span><span class="p">(</span><span class="s1">&#39;myMap&#39;</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">center</span><span class="o">:</span> <span class="p">[</span><span class="mf">4.2432838</span><span class="p">,</span> <span class="mf">51.9022471</span><span class="p">],</span> <span class="c1">// City of Rotterdam
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">zoom</span><span class="o">:</span> <span class="mi">12</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">style</span><span class="o">:</span> <span class="s1">&#39;satellite&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">view</span><span class="o">:</span> <span class="s1">&#39;Auto&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Add authentication details for connecting to Azure Maps.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">authOptions</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">authType</span><span class="o">:</span> <span class="s1">&#39;subscriptionKey&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">subscriptionKey</span><span class="o">:</span> <span class="s1">&#39;&lt;your key&gt;&#39;</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></div></div>
<h2 id="draw-a-flood-map">Draw a flood map</h2>
<p>Now that we have the basics we need only add a <a href="https://docs.microsoft.com/en-us/azure/azure-maps/map-add-bubble-layer" target="_blank" rel="noopener noreffer ">BubbleLayer</a> layer to the map, which we need to draw the floodmap with.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-js">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="c1">// Create a layer for rendering the elevation points.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">layer</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">atlas</span><span class="p">.</span><span class="nx">layer</span><span class="p">.</span><span class="nx">BubbleLayer</span><span class="p">(</span><span class="nx">datasource</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">color</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;interpolate&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">[</span><span class="s1">&#39;linear&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="p">[</span><span class="s1">&#39;get&#39;</span><span class="p">,</span> <span class="s1">&#39;elevation&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="mi">400</span><span class="p">,</span> <span class="s1">&#39;#006837&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="mi">450</span><span class="p">,</span> <span class="s1">&#39;#1a9850&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="mi">500</span><span class="p">,</span> <span class="s1">&#39;#66bd63&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="mi">550</span><span class="p">,</span> <span class="s1">&#39;#a6d96a&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="mi">600</span><span class="p">,</span> <span class="s1">&#39;#d9ef8b&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="mi">650</span><span class="p">,</span> <span class="s1">&#39;#ffffbf&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="mi">700</span><span class="p">,</span> <span class="s1">&#39;#fee08b&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="mi">750</span><span class="p">,</span> <span class="s1">&#39;#fdae61&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="mi">800</span><span class="p">,</span> <span class="s1">&#39;#f46d43&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="mi">850</span><span class="p">,</span> <span class="s1">&#39;#d73027&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="mi">900</span><span class="p">,</span> <span class="s1">&#39;#a50026&#39;</span>
</span></span><span class="line"><span class="cl">    <span class="p">],</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Don&#39;t outline the bubbles.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="c1">// This will make them blend together to create a heat map like visual.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">strokeWidth</span><span class="o">:</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="nx">map</span><span class="p">.</span><span class="nx">layers</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">layer</span><span class="p">);</span></span></span></code></pre></div></div>
<p>The <a href="https://docs.microsoft.com/en-us/javascript/api/azure-maps-drawing-tools/atlas.drawing.drawingmanager" target="_blank" rel="noopener noreffer ">DrawingManager</a> we not only use to draw the Bounding Box, but it gives us also the coordinates back we need for the <a href="https://docs.microsoft.com/en-us/rest/api/maps/elevation" target="_blank" rel="noopener noreffer ">Elevation API</a>.</p>
<p>The <em>Get Data</em> for Bounding Box API provides elevation data at equally spaced locations within a bounding box. A bounding box is defined by the coordinates for two corners (southwest, northeast) and then subsequently divided into rows and columns.</p>
<p>Elevations are returned for the vertices of the grid created by the rows and columns. Up to <em>2,000</em> elevations can be returned in a single request. The returned elevation values are ordered, starting at the southwest corner, and then proceeding west to east along the row. At the end of the row, it moves north to the next row, and repeats the process until it reaches the far northeast corner.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-js">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">elevationBoundsUrl</span> <span class="o">=</span> <span class="s1">&#39;https://{azMapsDomain}/elevation/lattice/json?api-version=1.0&amp;bounds={bounds}&amp;rows={rows}&amp;columns={columns}&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">url</span> <span class="o">=</span> <span class="nx">elevationBoundsUrl</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="s1">&#39;{bounds}&#39;</span><span class="p">,</span> <span class="nx">bounds</span><span class="p">).</span><span class="nx">replace</span><span class="p">(</span><span class="s1">&#39;{rows}&#39;</span><span class="p">,</span> <span class="nx">numRows</span><span class="p">).</span><span class="nx">replace</span><span class="p">(</span><span class="s1">&#39;{columns}&#39;</span><span class="p">,</span> <span class="nx">numColumns</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">url</span> <span class="o">=</span> <span class="nx">url</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="s1">&#39;{azMapsDomain}&#39;</span><span class="p">,</span> <span class="nx">atlas</span><span class="p">.</span><span class="nx">getDomain</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">processRequest</span><span class="p">(</span><span class="nx">url</span><span class="p">).</span><span class="nx">then</span><span class="p">(</span><span class="nx">response</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="nx">response</span><span class="p">.</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">alert</span><span class="p">(</span><span class="nx">response</span><span class="p">.</span><span class="nx">error</span><span class="p">.</span><span class="nx">message</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span><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></span><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">points</span> <span class="o">=</span> <span class="p">[];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Loop through the elevations, create point features with an elevation property.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">response</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">c</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">points</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="k">new</span> <span class="nx">atlas</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">Feature</span><span class="p">(</span><span class="k">new</span> <span class="nx">atlas</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">Point</span><span class="p">([</span><span class="nx">c</span><span class="p">.</span><span class="nx">coordinate</span><span class="p">.</span><span class="nx">longitude</span><span class="p">,</span> <span class="nx">c</span><span class="p">.</span><span class="nx">coordinate</span><span class="p">.</span><span class="nx">latitude</span><span class="p">]),</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">elevation</span><span class="o">:</span> <span class="nx">c</span><span class="p">.</span><span class="nx">elevationInMeter</span>
</span></span><span class="line"><span class="cl">    <span class="p">}));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span></code></pre></div></div>
<p>Now we only need to overwrite the points to the data source, and we have a flood / elevation map.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-js">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">datasource</span><span class="p">.</span><span class="nx">setShapes</span><span class="p">(</span><span class="nx">points</span><span class="p">);</span></span></span></code></pre></div></div>
]]></description></item><item><title>Create your own indoor maps using Azure Maps Creator</title><link>https://clemens.ms/create-your-own-indoor-maps-using-azure-maps-creator/</link><pubDate>Mon, 07 Jun 2021 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/create-your-own-indoor-maps-using-azure-maps-creator/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><h2 id="introduction">Introduction</h2>
<p>When you are working inside a building, like an office, factory, shopping mall, or something like a museum. There are probably a lot of sensors that can tell you information about that building, like what is the temperature and air quality per room, is there any door open or is there some alarm happening. When you in a big building and you want to know the fastest route to a specific room/store/painting, you probably apricate some help in navigating. These are all smart buildings.</p>
<blockquote>
<p><strong>Important:</strong> As of 2024, Azure Maps Creator has been deprecated and is no longer available. Microsoft has discontinued this service. If you&rsquo;re looking for indoor mapping solutions, please refer to the <a href="https://docs.microsoft.com/azure/azure-maps/" target="_blank" rel="noopener noreffer ">Azure Maps documentation</a> for current alternatives and migration guidance.</p></blockquote>
<p>Using indoor maps will not only help you visualize the building/floorplans and its IoT sensors, but also interact and navigate with it. For example, when you are for the first time in the <a href="https://www.bing.com/search?q=paris&#43;louvre&#43;museum" target="_blank" rel="noopener noreffer ">Louvre Museum</a> and want to know how to get to the famous <a href="https://www.bing.com/search?q=Mona%20Lisa" target="_blank" rel="noopener noreffer ">Mona Lisa</a> painting, you can use an interactive indoor map to find and navigate to what you were searching for.</p>
<p>By using the same technology for mapping outdoors we can use Azure Maps also for indoor mapping solutions. <a href="https://azure.microsoft.com/en-us/services/azure-maps/" target="_blank" rel="noopener noreffer ">Azure Maps</a> is a collection of geospatial services APIs and SDKs that use fresh mapping data to provide geographic context to web and mobile applications. <a href="https://azure.microsoft.com/en-us/updates/azure-maps-creator-is-now-generally-available/" target="_blank" rel="noopener noreffer ">Azure Maps Creator</a> is the service that facilitates the creation of indoor maps using your own building drawings. Azure Maps has SDKs for Web, Android, and iOS (is coming), and is available in <a href="https://docs.microsoft.com/en-us/azure/azure-maps/power-bi-visual-getting-started" target="_blank" rel="noopener noreffer ">Power BI</a>.</p>
<h2 id="azure-maps-creator">Azure Maps Creator</h2>
<p>To get started using the Azure Maps Creator services, you first need a free <a href="https://azure.microsoft.com/en-us/free/" target="_blank" rel="noopener noreffer ">Azure subscription</a> and an Azure Maps account. To use the Creator services, Azure Maps Creator must be created in an Azure Maps account. For information about how to create Azure Maps Creator in Azure Maps, see <a href="https://docs.microsoft.com/en-us/azure/azure-maps/how-to-manage-creator" target="_blank" rel="noopener noreffer ">Manage Azure Maps Creator</a>.</p>
<p>After you have created an Azure Maps account you get two options to authenticate to the Azure Maps services; a subscription API key or by using Azure Active Directory (what is a more secure way). The API key you use in the API calls. Currently, there are two endpoint regions: Europe, and the United States.</p>
<p>Second, you need to have or create your own CAD drawings from your building like floorplans in the <code>.DWG</code> file format (AutoCAD DWG file format version AC1032). These drawings need then be packaged into a simple <code>.ZIP</code> file including a <code>manifest.json</code> file that describes the Drawing package. See for more information, the <a href="https://docs.microsoft.com/en-us/azure/azure-maps/drawing-package-guide" target="_blank" rel="noopener noreffer ">Conversion Drawing package guide</a>. This step is very important, creating and preparing your floorplans including all the interactive elements that make your solution stand out or not.</p>
<p>The process after the drawing package creation is very straightforward. You upload the drawing package using the <a href="https://docs.microsoft.com/en-us/rest/api/maps/data-v2" target="_blank" rel="noopener noreffer ">Data Upload API</a>, convert it using the <a href="https://docs.microsoft.com/en-us/rest/api/maps/v2/conversion" target="_blank" rel="noopener noreffer ">Conversion API</a>, and create a dataset and tileset using the <a href="https://docs.microsoft.com/en-us/rest/api/maps/v2/dataset" target="_blank" rel="noopener noreffer ">Dataset API</a> and <a href="https://docs.microsoft.com/en-us/rest/api/maps/v2/tileset" target="_blank" rel="noopener noreffer ">Tileset API</a>. See this tutorial on how to do this step by step: <a href="https://docs.microsoft.com/en-us/azure/azure-maps/tutorial-creator-indoor-maps" target="_blank" rel="noopener noreffer ">Use Creator to create indoor maps</a>.</p>
<ul>
<li><strong>Dataset</strong> is a collection of indoor map features.</li>
<li><strong>Tileset is</strong> a collection of vector data that represents a set of uniform grid tiles.</li>
<li><strong>Feature statesets</strong> are collections of dynamic properties (states) that are assigned to dataset features, such as rooms or equipment.</li>
</ul>
<h2 id="rendering-the-indoor-map">Rendering the indoor map</h2>
<p>The <a href="https://docs.microsoft.com/en-us/azure/azure-maps/" target="_blank" rel="noopener noreffer ">Azure Maps Web SDK</a> includes the Indoor Maps module. This module offers extended functionalities to the Azure Maps <strong>Map Control library</strong>. The Indoor Maps module renders indoor maps created in Creator. It integrates widgets, such as floor picker, that help users visualize the different floors. The Indoor Maps module also supports dynamic map styling. For a step-by-step walkthrough to implement feature stateset dynamic styling in an application, see <a href="https://docs.microsoft.com/en-us/azure/azure-maps/how-to-use-indoor-module" target="_blank" rel="noopener noreffer ">Use the Indoor Map module</a>.</p>
<p>If you like to see what more is possible with the Azure Maps Web Control, there is a sample gallery with a collection of 270 code samples. See the <a href="https://samples.azuremaps.com/" target="_blank" rel="noopener noreffer ">Azure Maps Web SDK Samples</a>.</p>
]]></description></item><item><title>Protect your web applications using Azure Application Gateway</title><link>https://clemens.ms/protect-your-web-applications-using-azure-application-gateway/</link><pubDate>Tue, 06 Apr 2021 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/protect-your-web-applications-using-azure-application-gateway/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><h2 id="what-is-azure-application-gateway">What is Azure Application Gateway?</h2>
<p>Azure Application Gateway is a reverse proxy with optional WAF (Web Application Firewall) capability to allow incoming connections from external sources. The Gateway operates at Layer 3, 4, and 7 for IP-based, TCP/UDP-based, URL-based, and Host Header-based routing.</p>
<h2 id="when-to-use-the-application-gateway">When to use the Application Gateway?</h2>
<p>Microsoft has multiple services to protect and accelerate your applications; they are used for different scenarios, depending on where your users are:</p>
<ul>
<li><a href="https://docs.microsoft.com/en-us/azure/traffic-manager/traffic-manager-overview" target="_blank" rel="noopener noreffer ">Azure Traffic Manager</a> – DNS based load balancer across <strong>global regions</strong>
<ul>
<li><code>https://navatron.com</code></li>
<li><code>https://us.navatron.com</code></li>
<li><code>https://eu.navatron.com</code></li>
</ul>
</li>
<li><a href="https://docs.microsoft.com/en-us/azure/application-gateway/overview" target="_blank" rel="noopener noreffer ">Azure Application Gateway</a> – <strong>Single region</strong>, URL-based routing at the application
<ul>
<li><code>https://eu.navatron.com</code></li>
<li><code>https://eu.navatron.com/products</code></li>
<li><code>https://eu.navatron.com/support</code></li>
<li><code>https://eu.navatron.com/jobs</code></li>
</ul>
</li>
<li><a href="https://docs.microsoft.com/en-us/azure/frontdoor/front-door-overview" target="_blank" rel="noopener noreffer ">Azure Front Door</a> – <strong>Global</strong> load balancing with SSL offloading
<ul>
<li>IPv6</li>
<li>Application acceleration</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>Global</strong> Route clients to the closest available service region. Offload SSL and accelerate websites at the network edge.
<strong>Regional / Internal</strong> Route across zones and into your VNET. Private IP space routing and between your resources to build your regional application.</p></blockquote>
<h2 id="application-gateway-capabilities">Application Gateway Capabilities</h2>
<p>The v2 SKU has several enhancements which do not exist within the v1 SKU, allowing for additional capabilities.</p>
<blockquote>
<p><strong>see also:</strong></p>
<ul>
<li><a href="https://docs.microsoft.com/en-us/azure/application-gateway/application-gateway-autoscaling-zone-redundant#feature-comparison-between-v1-sku-and-v2-sku" target="_blank" rel="noopener noreffer ">Feature comparison between v1 SKU and v2 SKU</a></li>
</ul></blockquote>
<h3 id="scalability">Scalability</h3>
<p>Application Gateway or WAF deployments under Standard v2 or WAF v2 SKU support autoscaling and can scale up or down based on changing traffic load patterns. Autoscaling (up to 125 instances) also removes the requirement to choose a deployment size or instance count during provisioning. Application Gateway can operate both in fixed capacity (autoscaling disabled) and in autoscaling enabled mode. The v2 SKU offers performance enhancements and adds support for critical new features like autoscaling, zone redundancy, and support for static VIPs.</p>
<h3 id="zone-redundancy">Zone Redundancy</h3>
<p>An Application Gateway or WAF deployment can span multiple Availability Zones, removing the need to provision separate Application Gateway instances in each zone with a Traffic Manager. You can choose a single zone or multiple zones where Application Gateway instances are deployed, making it more resilient to zone failure. The back-end pool for applications can be similarly distributed across availability zones.</p>
<h3 id="static-vip">Static VIP</h3>
<p>The v1 SKU did not support a Static VIP and was only accessible via the configured URL. The v2 URL is only configurable with a Static VIP and will not change through reboots.</p>
<h3 id="aks-ingress-controller">AKS Ingress Controller</h3>
<p>The Application Gateway Ingress Controller allows Azure Application Gateway to be used as the ingress for an Azure Kubernetes Service (AKS) cluster. The ingress controller runs as a pod within the AKS cluster. It consumes Kubernetes Ingress Resources and converts them to an Azure Application Gateway configuration, allowing the Gateway to load-balance traffic to Kubernetes pods.</p>
<h3 id="key-vault-integration">Key Vault Integration</h3>
<p>Application Gateway v2 supports integration with Key Vault for server certificates that are attached to HTTPS-enabled listeners. You can either attach the certificate directly to the Application Gateway or provide a reference to an existing Key Vault certificate or secret when you create an HTTPS-enabled listener.</p>
<p>Integration includes many benefits such as:</p>
<ul>
<li>More robust security because the application development team doesn&rsquo;t directly handle SSL certificates. This integration allows a separate security team to:
<ul>
<li>Set up Application Gateways.</li>
<li>Control Application Gateway lifecycles.</li>
<li>Grant permissions to selected Application Gateways to access certificates that are stored in your Key Vault.</li>
</ul>
</li>
<li>Support for importing existing certificates into your key Vault. Or use Key Vault APIs to create and manage new certificates with any of the trusted Key Vault partners.</li>
<li>Support for automatic renewal of certificates that are stored in your Key Vault.</li>
</ul>
<p>Application Gateway currently supports software-validated certificates only. Hardware security module (HSM)-validated certificates are not supported. After Application Gateway is configured to use Key Vault certificates, its instances retrieve the certificate from Key Vault and install them locally for SSL termination. The instances also poll Key Vault at 24-hour intervals to retrieve a renewed version of the certificate if it exists. If an updated certificate is found, the SSL certificate currently associated with the HTTPS listener is automatically rotated.</p>
<h3 id="rewriting-httphttps-headers">Rewriting HTTP/HTTPS Headers</h3>
<p>HTTP headers allow a client and server to pass additional information with a request or response. By rewriting these headers, you can accomplish important tasks, such as adding security-related header fields like HSTS/ X-XSS-Protection, removing response header fields that might reveal sensitive information, and removing port information from X-Forwarded-For headers.</p>
<p>Application Gateway allows you to add, remove, or update HTTP request and response headers while the request and response packets move between the client and backend pools. And it allows you to add conditions to ensure that the specified headers are rewritten only when certain conditions are met.</p>
<p>Application Gateway also supports several server variables that help you store additional information about requests and responses. This makes it easier for you to create powerful rewrite rules.</p>
<p>All headers in requests and responses can be modified, except for the Host, Connection, and Upgrade headers.</p>
<h3 id="socket-routing">Socket Routing</h3>
<p>Web Application Gateway can make routing decisions based on the IP, and Port (Socket) requests. This allows for one Application Gateway to front-end multiple applications.</p>
<h3 id="url-routing">URL Routing</h3>
<p>URL Path-Based Routing allows you to route traffic to backend server pools based on the request&rsquo;s URL Paths. One of the scenarios is to route requests for different content types to another pool.  For example, requests for <code>https://navatron.com/video/*</code> are routed to <em>VideoServerPool</em>, and <code>https://navatron.com/images/*</code> are routed to <em>ImageServerPool</em>. <em>DefaultServerPool</em> is not sent any traffic in this example.</p>
<p>Rules are processed in the order they are listed in the portal. It is highly recommended to configure multi-site listeners first prior to configuring a basic listener. This ensures that traffic gets routed to the proper back end. If a basic listener is listed first and matches an incoming request, it gets processed by that listener.</p>
<h3 id="multiple-site-hosting">Multiple-Site Hosting</h3>
<p>Multiple-site hosting enables you to configure more than one website on the same Application Gateway instance. This feature allows you to configure a more efficient topology for your deployments by adding up to 100 websites to one application gateway. Each website can be directed to its own pool. For example, Application Gateway can serve traffic for <code>navatron.com</code> and <code>navatron.nl</code> from two server pools called <em>MainServerPool</em> and <em>DutchServerPool</em>.</p>
<p>Similarly, two subdomains of the same parent domain can be hosted on the same application gateway deployment. Examples of using subdomains could include <code>https://blog.navatron.com</code> and <code>https://api.navatron.com</code> hosted on a single Application Gateway deployment.</p>
<h3 id="redirection">Redirection</h3>
<p>Often, Web servers will contain pools specifically for web redirection from HTTP to HTTPS.  Application Gateway can handle this scenario natively, simplifying configs and freeing up resources on the web servers themselves.  This is a generic redirection mechanism to redirect from and to any port you define using rules. It also supports redirection to an external site as well.</p>
<h3 id="optional-waf">Optional WAF</h3>
<p>Web application firewall (WAF) is a feature of Application Gateway that provides centralized protection of web applications from common exploits and vulnerabilities. WAF is based on rules from the OWASP (Open Web Application Security Project) core rule sets 3.0 or 2.2.9.</p>
<p>Web applications are increasingly targeted for malicious attacks that exploit commonly known vulnerabilities. Preventing such attacks in application code can be challenging and may require rigorous maintenance, patching, and monitoring at many application topology layers. A centralized web application firewall helps make security management much more straightforward and reassures application administrators against threats or intrusions. A WAF solution can also react to a security threat faster by patching a known vulnerability at a central location versus securing each of individual web applications. Existing application gateways can be converted to a web application firewall-enabled application gateway easily.</p>
<p>Application Gateway WAF protects against:</p>
<ul>
<li>SQL-injection</li>
<li>Cross-site scripting</li>
<li>Other common web attacks, such as command injection, HTTP request smuggling, HTTP response splitting, and remote file inclusion</li>
<li>HTTP protocol violations</li>
<li>HTTP protocol anomalies, such as missing host user-agent and accept headers</li>
<li>Bots, crawlers, and scanners</li>
<li>Common application misconfigurations (for example, Apache and IIS) through detection</li>
</ul>
<p>These WAF Features can be enabled in monitoring/learning mode or in enforcing mode. All the findings can be reviewed via various monitoring configurations like Azure Monitor, Azure Security Center, or Azure Diagnostics Logs.</p>
<h3 id="end-to-end-ssl">End-to-End SSL</h3>
<p>When SSL Encryption must be maintained, Application Gateway can be configured for End-to-End SSL. In this process, the Application Gateway will decrypt the traffic, review and evaluate based on configured policies, modify the headers as configured, and then re-encrypt traffic before sending it to the back-end servers.</p>
<blockquote>
<p><strong>see also:</strong></p>
<ul>
<li><a href="https://docs.microsoft.com/en-us/azure/application-gateway/ssl-overview" target="_blank" rel="noopener noreffer ">Overview of TLS termination and end to end TLS with Application Gateway</a></li>
</ul></blockquote>
<h3 id="ssl-termination">SSL Termination</h3>
<p>Application gateway supports SSL/TLS termination at the Gateway, after which traffic typically flows unencrypted to the back-end servers. This feature allows web servers to be unburdened from costly encryption and decryption overhead.</p>
<blockquote>
<p><strong>see also:</strong></p>
<ul>
<li><a href="https://docs.microsoft.com/en-us/azure/application-gateway/key-vault-certs" target="_blank" rel="noopener noreffer ">TLS termination with Key Vault certificates</a></li>
</ul></blockquote>
<h3 id="session-affinity">Session Affinity</h3>
<p>The cookie-based session affinity feature is useful when you want to keep a user session on the same server. The Application Gateway can direct subsequent traffic from a user session to the same server for processing by using gateway-managed cookies. This is important in cases where the session state is saved locally on the server for a user session.</p>
<h3 id="custom-error-pages">Custom Error Pages</h3>
<p>Application Gateway allows for the creation of custom error pages instead of displaying the default error pages. Custom branding and layout can be used within a custom error page.</p>
<p>For example, a custom maintenance page can be displayed if the application isn&rsquo;t reachable or an unauthorized access page if a malicious request is sent to a web application. These custom error pages can replace HTTP 502 and 403 return codes.</p>
<p>If an error originates from the back-end servers, then it&rsquo;s passed along unmodified back to the caller. A custom error page isn&rsquo;t displayed. Application Gateway can display a custom error page when a request can&rsquo;t reach the back-end.</p>
<h3 id="websocket-support">WebSocket Support</h3>
<p>Application Gateway provides native support for the WebSocket protocol. There is no user-configurable setting to selectively enable or disable WebSocket support. The WebSocket protocol enables full-duplex communication between a server and a client over a long-running TCP connection.</p>
<h3 id="http2-support">HTTP/2 Support</h3>
<p>Application Gateway provides native support for the HTTP/2 protocol. The HTTP/2 protocol enables full-duplex communication between a server and a client over a long-running TCP connection. This allows for a more interactive communication between the web server and the client, which can be bidirectional without the need for polling as required in HTTP-based implementations. These protocols have low overhead, unlike HTTP, and can reuse the same TCP connection for multiple requests/responses, resulting in a more efficient resource utilization. These protocols are designed to work over traditional HTTP ports of 80 and 443.</p>
<h3 id="connection-draining">Connection Draining</h3>
<p>Connection draining helps you achieve graceful removal of back-end pool members during planned service updates. This setting is enabled via the back-end HTTP setting and can be applied to all back-end pool members during rule creation. Once enabled, Application Gateway ensures all de-registering instances of a back-end pool do not receive any new requests while allowing existing requests to complete within a configured time limit. This applies to both back-end instances that are explicitly removed from the back-end pool by an API call, and back-end instances reported as unhealthy as determined by the health probes.</p>
<h3 id="health-probes">Health Probes</h3>
<p>Azure Application Gateway, by default, monitors the health of all resources in its back-end pool and automatically removes any resource considered unhealthy from the pool. Application Gateway continues to monitor the unhealthy instances and adds them back to the healthy back-end pool once they become available and respond to health probes. Application gateway sends the health probes with the same port that is defined in the back-end HTTP settings. This configuration ensures that the probe is testing the same port that customers would be using to connect to the back-end.  In addition to using default health probe monitoring, you can also customize the health probe to suit your application&rsquo;s requirements.</p>
<blockquote>
<p><strong>see also:</strong></p>
<ul>
<li><a href="https://docs.microsoft.com/en-us/azure/application-gateway/application-gateway-probe-overview" target="_blank" rel="noopener noreffer ">Application Gateway health monitoring overview</a></li>
</ul></blockquote>
<h3 id="networking">Networking</h3>
<p>Azure Application Gateways are always deployed in a virtual network subnet. This subnet can only contain Application Gateways. Although this is an entirely managed service, behind the scenes, Azure is deploying one or more instances of load-balanced virtual appliances into your VNet.</p>
<ul>
<li>Application Gateway can talk to instances outside of the virtual network as long as there is IP connectivity.</li>
<li>Network Security Groups are supported on the Application Gateway subnet to provide segmentation/isolation.</li>
<li>Front-End IPs on an Application Gateway can be either public or private depending on requirements.</li>
</ul>
]]></description></item><item><title>Infrastructure as Code</title><link>https://clemens.ms/infrastructure-as-code/</link><pubDate>Tue, 30 Mar 2021 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/infrastructure-as-code/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><h2 id="infrastructure-as-code">Infrastructure as Code</h2>
<p>Infrastructure as Code (IaC) is the process of managing and provisioning infrastructure and configuration dependencies for application stacks using machine-readable definition files rather than physical hardware configuration or interactive configuration tools.</p>
<h3 id="what-is-infrastructure-as-code">What is Infrastructure as Code?</h3>
<ul>
<li>Source Controlled</li>
<li>In code (Scripts &amp; Templates)</li>
<li>Automated &amp; Continuous Deployment</li>
<li>Testing</li>
<li>Feedback loop (Monitoring)</li>
</ul>
<h3 id="categories-of-iac-tooling">Categories of IaC tooling</h3>
<p>You can use many declarative <a href="https://docs.microsoft.com/en-us/azure/architecture/framework/devops/automation-infrastructure" target="_blank" rel="noopener noreffer ">infrastructure deployment technologies</a> with Azure. These fall into two main categories.</p>
<ul>
<li>Imperative IaC involves writing scripts in a language like Bash, PowerShell, C# script files, or Python. These programmatically execute a series of steps to create or modify your resources. When using imperative deployments, it is up to you to manage things like dependency sequencing, error control, and resource updates.</li>
<li>Declarative IaC involves writing a definition of how you want your environment to look; the tooling then figures out how to make this happen by inspecting your current state, comparing it to the target state you&rsquo;ve requested, and applying the differences.</li>
</ul>
<h2 id="terraform">Terraform</h2>
<p><a href="https://www.terraform.io/" target="_blank" rel="noopener noreffer ">Hashicorp Terraform</a> is an open-source tool for provisioning and managing cloud infrastructure. It codifies infrastructure in configuration files that describe the topology of cloud resources. These resources include virtual machines, storage accounts, and networking interfaces. The Terraform CLI provides a simple mechanism to deploy and version the configuration files to Azure.</p>
<blockquote>
<p><strong>See also:</strong></p>
<ul>
<li><a href="https://docs.microsoft.com/en-us/azure/developer/terraform/overview" target="_blank" rel="noopener noreffer ">Terraform with Azure</a></li>
<li><a href="https://geekzter.medium.com/using-terraform-with-azure-azure-pipelines-github-actions-86e043bd0d9e" target="_blank" rel="noopener noreffer ">Using Terraform together with Azure, Azure Pipelines &amp; GitHub Actions</a></li>
</ul></blockquote>
<p>Infrastructure as Code with Terraform has become pervasive for provisioning cloud infrastructure. Azure engineering has invested in Terraform AzureRM provider as a first-party control plane, of equal importance to Azure-Cli and PowerShell, for Azure. As companies mature in their use of Terraform, there is an increasing need to apply DevOps practices to their IaC development to increase velocity and reduce Mean-Time-To-Recover from failures.</p>
<blockquote>
<p><strong>See also:</strong></p>
<ul>
<li><a href="https://docs.microsoft.com/en-us/azure/architecture/solution-ideas/articles/immutable-infrastructure-cicd-using-jenkins-and-terraform-on-azure-virtual-architecture-overview" target="_blank" rel="noopener noreffer ">Immutable Infrastructure</a> CI/CD using Jenkins and Terraform on Azure Virtual Architecture.</li>
</ul></blockquote>
<h3 id="challenges">Challenges</h3>
<p>Doing automation around terraform has several key challenges:</p>
<ul>
<li>How you deploy in production <strong>MUST</strong> match how you deploy in development and testing?</li>
<li>Testing requires that physical resources be deployed before the test.</li>
<li>State management is challenging in multi-user/multi-team scenarios.</li>
<li>Not a lot of best practices around how to segment/manage remote state &amp; workspaces.</li>
</ul>
<h3 id="testing">Testing</h3>
<p>Terraform enables the definition, preview, and deployment of cloud infrastructure. Using Terraform, you create configuration files using HCL syntax. The HCL syntax allows you to specify the cloud provider - such as Azure - and the elements that make up your cloud infrastructure. After you create your configuration files, you create an execution plan that allows you to preview your infrastructure changes before they&rsquo;re deployed. Once you verify the changes, you apply the execution plan to deploy the infrastructure.</p>
<blockquote>
<p><strong>See also:</strong></p>
<ul>
<li>Best practices <a href="https://docs.microsoft.com/en-us/azure/developer/terraform/best-practices-testing-overview" target="_blank" rel="noopener noreffer ">Terraform testing</a> overview.</li>
<li>Automated testing with <a href="https://github.com/gruntwork-io/terratest" target="_blank" rel="noopener noreffer ">Terratest</a>. Terratest is a critical component in the toolchain needed for effective terraform development.</li>
</ul></blockquote>
<h2 id="project-lucidity">Project Lucidity</h2>
<p><a href="https://github.com/rguthrie-ghec/Terraform-Pipelines" target="_blank" rel="noopener noreffer ">Project Lucidity</a> allows you to deploy infrastructure to Azure using Terraform easily. It addresses some of the challenges from above:</p>
<ul>
<li>An opinionated approach to terraform development on Azure using Azure DevOps</li>
<li>Flexible installation script that creates a project with pipelines configured so you can focus on writing terraform and creating test</li>
<li>Minimize fixed design choices while adhering to best practices
<ul>
<li>The directory structure for maintaining Terraform modules and deployments.</li>
<li>Use environment variables stored in .env files instead of .tfvars</li>
<li>Use Terratest to test your terraform code as an acceptance test</li>
<li>Secrets should only ever live in KeyVault.</li>
</ul>
</li>
</ul>
<h2 id="azure-bicep">Azure Bicep</h2>
<p><a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/bicep-overview" target="_blank" rel="noopener noreffer ">Azure Bicep</a> is an abstraction built on top of Azure ARM Templates and Azure Resource Manager that offers a cleaner code syntax with better support for modularity and code reuse. Azure Bicep moves away from the JSON syntax used by ARM Templates and is much easier to read and write Infrastructure as Code (IaC) in Azure.</p>
<h3 id="what-is-azure-bicep">What is Azure Bicep?</h3>
<p>Azure Bicep is a new declarative Domain Specific Language (DSL) for deploying Azure resources. This new language aims to make it easier to write Infrastructure as Code (IaC) targeting Azure Resource Manager (ARM) using a syntax that&rsquo;s more friendly than the JSON syntax of Azure ARM Templates.</p>
<p>Azure Bicep works as an abstraction layer built on top of ARM Templates. Anything that can be done with Azure ARM Templates can be done with Azure Bicep as it provides a &ldquo;transparent abstraction&rdquo; over ARM (Azure Resource Manager). With this abstraction, all the types, API versions, and properties valid within ARM Templates are also valid with Azure Bicep.</p>
<p>Azure Bicep is a compiled language. This means that the Azure Bicep code was converted into ARM Template code. Then, the resulting ARM Template code is used to deploy the Azure resources. This enables Azure Bicep to use its syntax and compiler for authoring Azure Bicep files that compile down to Azure Resource Manager (ARM) JSON as a sort of intermediate language (IL).</p>
<h2 id="security-best-practices-for-iac">Security Best Practices for IaC</h2>
<p>The security of IaC definitions, deployments, and operational workloads are critically important due to the high level of permissions required to implement an IaC solution effectively. This chapter attempts to outline key best practices that contribute to secure IaC strategies.</p>
<h3 id="use-directory-services-for-authentication">Use directory services for authentication</h3>
<p>Directory services enable you to safeguard user and system credentials by enforcing strong authentication and conditional access policies. These technologies allow you to manage your identities by ensuring that the right services have the right access to the right resources.</p>
<ul>
<li>Use directory services, like <a href="https://azure.microsoft.com/en-us/services/active-directory/" target="_blank" rel="noopener noreffer ">Azure Active Directory</a>, for authenticating users and services when deploying infrastructure so that Role-Based-Access-Control can be used to apply the principle of least privilege.</li>
<li>When deploying infrastructure, use a dedicated identity so that the blast radius of a leaked credential is scoped as narrowly as possible.</li>
<li>When deploying infrastructure, leverage platform authentication technologies that avoid needing to store a username and password to make it harder for credentials to leak. In practice, this means leveraging <a href="https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview" target="_blank" rel="noopener noreffer ">Managed Identities</a> or <a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/library/connect-to-azure?view=azure-devops" target="_blank" rel="noopener noreffer ">Service Connections</a> in Azure DevOps.</li>
<li>When a username or password must be used based on technology limitations, all sensitive information should be stored in a secret store such as <a href="https://clemens.ms/azure-key-vault/" target="_blank" rel="noopener noreffer ">Key Vault</a> and masked in any logging output so that secrets are not leaked during deployments.</li>
</ul>
<h3 id="rotate-deployment-credentials">Rotate Deployment Credentials</h3>
<p>Credential rotation refers to the changing/resetting of a credential(s). Limiting a credential&rsquo;s lifespan reduces the risk from and effectiveness of credential-based attacks and exploits by condensing the window of time during which a stolen credential may be valid.</p>
<ul>
<li>All secrets required to authenticate to external services (i.e., Azure) should expire on a regular cadence so that any leaked credentials are not valid indefinitely.</li>
<li>Design automated credential rotation from the beginning of a project so that the delivered solution can recover from a security breach quickly.</li>
</ul>
<h3 id="apply-the-principle-of-least-privilege">Apply the principle of least privilege</h3>
<p>The principle of least privilege (PoLP), also known as the principle of minimal privilege or the principle of least authority, requires that in a particular abstraction layer of a computing environment, every module (such as a process, a user, or a program, depending on the subject) must be able to access only the information and resources that are necessary for its legitimate purpose. Limiting the access of a computing-environment reduces the risk from and effectiveness of credential-based attacks and exploits by minimizing the blast radius of a leaked credential.</p>
<ul>
<li>Identities used for infrastructure deployments should have a minimal set of privileges so that a leaked credential has the smallest blast radius possible.</li>
<li>Use separate identities for deploying to each environment (i.e., dev, QA, prod) so that a leaked credential for one of those environments does not grant access to another environment. This also protects against a bug being tested in a dev IaC deployment from unintentionally impacting non-test environments.</li>
</ul>
<h3 id="manage-secrets-securely">Manage secrets securely</h3>
<p>Secure secrets management is essential to protect data in the cloud and can be used to store sensitive information like passwords and certificates securely. Managed secret stores often provide the ability to protect sensitive information using strong encryption while avoiding the need to keep secrets in a repository or as a plain text environment variable.</p>
<ul>
<li>Secrets required for deployments, such as database credentials or private certificates, should be referenced from a secure vault to be stored at rest with encryption and are only accessible via authenticated requests.</li>
</ul>
<h3 id="deploy-through-automated-pipelines">Deploy through automated pipelines</h3>
<p>CI/CD pipelines are at the core of daily operations for many businesses today. When set up correctly, these processes help keep the delivery process consistent by automating many manual tasks and providing visibility into how the software is being worked on.</p>
<ul>
<li>Infrastructure deployments to non-development environments should only be done through repeatable processes such as a continuous deployment pipeline so that it is not possible for the deployed environment to drift from the environment as described through IaC templates.</li>
</ul>
<h3 id="enforce-branch-policies-and-prs">Enforce branch policies and PRs</h3>
<p>Branch policies are an essential part of the Git workflow and enable you to: Isolate work in progress from the completed work in your master branch. Guarantee changes build before they get to Main. Limit who can contribute to specific branches.</p>
<ul>
<li>Changes to IaC should only be deployed to non-development environments after being reviewed by the team and merged into the appropriate branch so that malicious or unintended changes to infrastructure are not deployed into production environments. See Configure <a href="https://docs.microsoft.com/en-us/azure/devops/repos/git/branch-policies-overview?view=azure-devops" target="_blank" rel="noopener noreffer ">Branch Policies</a> in Azure DevOps.</li>
</ul>
<h3 id="use-gated-approvals-in-automated-releases">Use gated approvals in automated releases</h3>
<p>Approvals and gates give you additional control over the start and completion of the deployment pipeline. Each stage in a release pipeline can be configured with pre-deployment and post-deployment conditions, including waiting for users to manually approve or reject deployments and checking with other automated systems until specific conditions are verified.</p>
<ul>
<li>Infrastructure deployments should not be deployed into customer-facing environments without the explicit approval of an application operator so that they can monitor the application and its dependencies after the deployment completes. See <a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/release/approvals/?view=azure-devops" target="_blank" rel="noopener noreffer ">Release Approvals and Gates</a> Overview.</li>
</ul>
<h2 id="azure-blueprints">Azure Blueprints</h2>
<p>Just as a blueprint allows an engineer or an architect to sketch a project&rsquo;s design parameters, <a href="https://azure.microsoft.com/en-us/services/blueprints/" target="_blank" rel="noopener noreffer ">Azure Blueprints</a> enables cloud architects and central information technology groups to define a repeatable set of Azure resources that implements and adheres to an organization&rsquo;s standards, patterns, and requirements. Azure Blueprints makes it possible for development teams to rapidly build and stand-up new environments with trust they&rsquo;re building within organizational compliance with a set of built-in components, such as networking, to speed up development and delivery.</p>
<p>Blueprints are a declarative way to orchestrate the deployment of various resource templates and other artifacts such as:</p>
<ul>
<li>Role Assignments</li>
<li>Policy Assignments</li>
<li>Azure Resource Manager templates (ARM templates)</li>
<li>Resource Groups</li>
</ul>
<p>The Azure Blueprints service is backed by the globally distributed Azure Cosmos DB. Blueprint objects are replicated to multiple Azure regions. This replication provides low latency, high availability, and consistent access to your blueprint objects, regardless of which region Azure Blueprints deploys your resources to.</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/cQ9D-d6KkMY?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<h3 id="how-its-different-from-arm-templates">How it&rsquo;s different from ARM templates</h3>
<p>The service is designed to help with environment setup. This setup often consists of a set of resource groups, policies, role assignments, and ARM template deployments. A blueprint is a package to bring each of these artifact types together and allow you to compose and version that package, including through continuous integration and continuous delivery (CI/CD) pipeline. Ultimately, each is assigned to a subscription in a single operation that can be audited and tracked.</p>
<p>Nearly everything that you want to include for deployment in Azure Blueprints can be accomplished with an ARM template. However, an ARM template is a document that doesn&rsquo;t exist natively in Azure – each is stored either locally or in source control. The template gets used for deployments of one or more Azure resources, but once those resources deploy there&rsquo;s no active connection or relationship to the template.</p>
<p>With Azure Blueprints, the relationship between the blueprint definition (what should be deployed) and the blueprint assignment (what was deployed) is preserved. This connection supports improved tracking and auditing of deployments. Azure Blueprints can also upgrade several subscriptions at once that are governed by the same blueprint.
There&rsquo;s no need to choose between an ARM template and a blueprint. Each blueprint can consist of zero or more ARM template artifacts. This support means that previous efforts to develop and maintain a library of ARM templates are reusable in Azure Blueprints.</p>
<h3 id="how-its-different-from-azure-policy">How it&rsquo;s different from Azure Policy</h3>
<p>A blueprint is a package or container for composing focus-specific sets of standards, patterns, and requirements related to the implementation of Azure cloud services, security, and design that can be reused to maintain consistency and compliance.</p>
<p>A policy is a default allow and explicit deny system focused on resource properties during deployment and for already existing resources. It supports cloud governance by validating that resources within a subscription adhere to requirements and standards.</p>
<p>Including a policy in a blueprint enables the creation of the right pattern or design during assignment of the blueprint. The policy inclusion makes sure that only approved or expected changes can be made to the environment to protect ongoing compliance to the intent of the blueprint.</p>
<p>A policy can be included as one of many artifacts in a blueprint definition. Blueprints also support using parameters with policies and initiatives.</p>
<h3 id="blueprint-definition-locations">Blueprint definition locations</h3>
<p>When creating a blueprint definition, you&rsquo;ll define where the blueprint is saved. Blueprints can be saved to a management group or <strong>parent</strong> subscription that you have Contributor access to. If the location is a management group, the blueprint is available to assign to any <strong>child</strong> subscription of that management group.</p>
<h3 id="managing-blueprints-as-code">Managing Blueprints as Code</h3>
<p>Using the Blueprints in the Azure Portal is a great way to get started with Blueprints or to use Blueprints on a small-ish scale, but often you&rsquo;ll want to manage your <a href="https://github.com/Azure/azure-blueprints" target="_blank" rel="noopener noreffer ">Blueprints-as-Code</a> for a variety of reasons, such as:</p>
<ul>
<li>Sharing blueprints</li>
<li>Keeping blueprints in source control</li>
<li>Putting blueprints in a CI/CD or release pipeline</li>
</ul>
<h2 id="testing-strategies">Testing strategies</h2>
<p><a href="https://clemens.ms/compliance-as-code/" target="_blank" rel="noopener noreffer ">Compliance-as-Code</a> can be summarized as the organizational capability to automate the implementation, verification, remediation, monitoring, and compliance status reporting. This automation comes in the form of code and is integrated into the code repositories used by Devs and Engineers. It becomes &ldquo;just another piece of code.&rdquo; I have written an entire blog post about <a href="https://clemens.ms/compliance-as-code/" target="_blank" rel="noopener noreffer ">Compliance-as-Code</a> here.</p>
]]></description></item><item><title>Compliance as Code</title><link>https://clemens.ms/compliance-as-code/</link><pubDate>Mon, 22 Mar 2021 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/compliance-as-code/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><h2 id="compliance-as-code">Compliance-as-Code</h2>
<h3 id="what-is-compliance-as-code">What is compliance as code?</h3>
<p>Compliance-as-Code can be summarized as the organizational capability to <strong>automate</strong> the implementation, verification, remediation, monitoring, and compliance status reporting. This automation comes in the form of code and is integrated into the code repositories used by Devs and Engineers. It becomes &ldquo;just another piece of code.&rdquo;</p>
<ul>
<li>Using code to describe, validate, (possibly) remediate, monitor, and report compliance requirements and status</li>
<li>Measured against regulatory standards and internal governance</li>
<li>Includes (but not limited to):
<ul>
<li>Security</li>
<li>Infrastructure configuration</li>
<li>Privacy</li>
<li>Policies: Government, finance, health, etc.</li>
<li>Licensing (i.e., Open Source)</li>
</ul>
</li>
</ul>
<h3 id="why-use-compliance-as-code">Why use compliance as code?</h3>
<p>Just as with any other *aC, precision and repeatability of code execution eliminate human error.</p>
<ul>
<li>Precisions and repeatability</li>
<li>Reduced effort on repetition</li>
<li>Integrates compliance into daily practices, mitigating compliance knowledge silos</li>
<li>Enables and simplifies audits through programmatically defined evidence gathering processes</li>
</ul>
<h3 id="how-to-use-compliance-as-code">How to use compliance as code?</h3>
<p>Code the requirements as a test. Use descriptive approaches as PowerShell Desired State Configuration (DSC) vs just code as they are simpler to understand, read and maintain. Run compliance tests whenever possible, not just at later stages. Run them while in production to detect unexpected changes (drifts) from the described initially.</p>
<ul>
<li>Implemented as tests (multiple levels)</li>
<li>Promote descriptive approaches vs pure code</li>
<li>Run compliance tests early and frequently (shift left)</li>
<li>Run compliance tests late to detect breaches and drifts (shift right)</li>
<li>Multiple tools available: PowerShell DSC, <a href="https://www.inspec.io/" target="_blank" rel="noopener noreffer ">InsPec</a>, <a href="https://www.openpolicyagent.org/" target="_blank" rel="noopener noreffer ">Open Policy Agent</a></li>
</ul>
<h2 id="infrastructure-as-code">Infrastructure-as-Code</h2>
<p>Infrastructure-as-Code (IaC) is a well-known approach by which the infrastructure requirements are described in code and configuration files. In turn, when executed, it generates the infrastructural artifacts necessary to support the solution.</p>
<p>With the advent of virtual infrastructure, IaC found its space and brought all the advantages of code (speed, repeatability, and human error removal). But as with any other artifacts of a solution, we need to validate and enforce quality.</p>
<p>IaC is only an effective tool when you incorporate this into your DevOps process, and you are willing to run tests and validate your code continuously. Otherwise, you run serious risks when it comes to application security and function. With IaC, your applications can be continually updated while running, and everything is still under compliance. This is a significant advantage, but it requires a certain degree of monitoring to react as unknown issues arise.</p>
<h3 id="challenges">Challenges</h3>
<ul>
<li>The declarative approach is hard to test</li>
<li>Long test cycles</li>
<li>The complexity of modern architectures</li>
<li>Highly distributed systems</li>
</ul>
<h3 id="different-validation-strategies">Different Validation Strategies</h3>
<h4 id="static-or-style-checks">Static or style checks</h4>
<ul>
<li>Visual inspection</li>
<li>Validates format, naming conventions, structure</li>
<li>Static code analysis, linting, etc.</li>
<li>Can be executed during build pipelines</li>
</ul>
<h4 id="unit-tests">Unit tests</h4>
<ul>
<li>Validate the correct functionality of each IaC file/directive</li>
<li>Executed after the IaC has been deployed</li>
<li>Executed in the release phase/pipeline</li>
</ul>
<h4 id="integration-and-system-tests">Integration and system tests</h4>
<ul>
<li>Validates the complete flow of the multiple IaC artifacts</li>
<li>Executed after passing unit tests</li>
<li>Typically used in Integration/Quality/Pre-production/Production</li>
<li>Excellent tool for blue/green deployment strategies</li>
</ul>
<h4 id="monitoring">Monitoring</h4>
<ul>
<li>Validates alignment with predefined</li>
<li>Identifies drifts</li>
<li>Excellent tool for monitoring platform stability and health</li>
</ul>
<h2 id="testing-tools">Testing Tools</h2>
<h3 id="pester">Pester</h3>
<p><a href="https://pester.dev/" target="_blank" rel="noopener noreffer ">Pester</a> is an testing- and mocking framework for PowerShell. Pester is most commonly used for writing unit and integration tests. It is also a base for tools that validate whole environments, cloud deployments, database configurations, etc.</p>
<p>Pester follows a file naming convention *.Tests.ps1, and uses a simple set of functions: <code>Describe</code>, <code>Context</code>, <code>It</code>, <code>Should</code>, and <code>Mock</code> to create a mini-DSL for writing tests.</p>
<p>Tests can be run locally and should be integrated into a build script in a CI pipeline. Pester can produce artifacts such as <strong>Code Coverage</strong> and <strong>Test Result</strong> files for reporting results in CI pipeline.</p>
<p>We test our implementation by validating within our tests that the requirements are present in our configuration file. If changes to the ARM template happen, we can still ensure our requirements are met if all tests are passed. The output of these tests is written in a human-readable way and can be interpreted by non-technical people.</p>
<p>In this example, we will query for the Storage Accounts. The object returned will have all configured properties. Otherwise, we can get the deployed resource by using:</p>
<p><code>Get-AzResource -ResourceType 'Microsoft.Storage/storageAccounts’</code></p>
<p>To ensure the specification is met, we need to add assertions based on the specification. These assertions should validate that the properties are set correctly on the deployed Azure Resource.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-powershell">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="c"># adls.acceptance.spec.ps1</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$adls</span> <span class="p">=</span> <span class="nb">Get-AzStorageAccount</span> <span class="n">-Name</span> <span class="nv">$resource</span><span class="p">.</span><span class="py">Name</span> <span class="n">-ResourceGroupName</span> <span class="nv">$resource</span><span class="p">.</span><span class="py">ResourceGroupName</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">Describe</span> <span class="s2">&#34;</span><span class="nv">$Name</span><span class="s2"> Data Lake Storage Account Generation 2&#34;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c"># Mandatory requirement of ADLS Gen 2 are:</span>
</span></span><span class="line"><span class="cl">    <span class="c"># - Resource Type is Microsoft.Storage/storageAccounts, </span>
</span></span><span class="line"><span class="cl">    <span class="c">#   as we know we are looking for this it is obsolete to check</span>
</span></span><span class="line"><span class="cl">    <span class="c"># - Kind is StorageV2</span>
</span></span><span class="line"><span class="cl">    <span class="c"># - Hierarchical namespace is enabled</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">it</span> <span class="s2">&#34;should be of kind StorageV2&#34;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$adls</span><span class="p">.</span><span class="py">Kind</span> <span class="p">|</span> <span class="n">Should</span> <span class="n">-Be</span> <span class="s2">&#34;StorageV2&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">it</span> <span class="s2">&#34;should have Hierarchical Namespace Enabled&#34;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$adls</span><span class="p">.</span><span class="py">EnableHierarchicalNamespace</span> <span class="p">|</span> <span class="n">Should</span> <span class="n">-Be</span> <span class="vm">$true</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</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></div></div>
<h3 id="inspec">Inspec</h3>
<p><a href="https://community.chef.io/tools/chef-inspec/" target="_blank" rel="noopener noreffer ">Inspec</a> is a command line, an open-source tool provided by Chef with an audit and automated testing framework for integration, compliance, and security. It does not require learning a new language, just knowing how to write the desired state of infrastructure resources. With Inspec, you can test the compliance of remotes machines, data, and cloud infrastructures like Azure and others.</p>
<ul>
<li><strong>Test the desired state</strong> Verify your applications and infrastructure&rsquo;s current desired state according to the code you write.</li>
<li><strong>Human-readable code</strong> Reduce friction by writing tests that are easy to understand by anyone.</li>
<li><strong>Extensible</strong> Create custom resources with ease and share them easily with others</li>
</ul>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-Ruby">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Ruby" data-lang="Ruby"><span class="line"><span class="cl"><span class="n">describe</span> <span class="n">file</span><span class="p">(</span><span class="s1">&#39;/etc/myapp.conf&#39;</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">exist</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"> 
</span></span><span class="line"><span class="cl"><span class="n">its</span><span class="p">(</span><span class="s1">&#39;mode&#39;</span><span class="p">)</span> <span class="p">{</span> <span class="n">should</span> <span class="n">cmp</span> <span class="mo">0644</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">describe</span> <span class="n">apache_conf</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl"> 
</span></span><span class="line"><span class="cl"><span class="n">its</span><span class="p">(</span><span class="s1">&#39;Listen&#39;</span><span class="p">)</span> <span class="p">{</span> <span class="n">should</span> <span class="n">cmp</span> <span class="mi">8080</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">describe</span> <span class="n">port</span><span class="p">(</span><span class="mi">8080</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">it</span> <span class="p">{</span> <span class="n">should</span> <span class="n">be_listening</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div>
<h3 id="azure-resource-manager-template-toolkit">Azure Resource Manager Template Toolkit</h3>
<p>The <a href="https://github.com/Azure/arm-ttk" target="_blank" rel="noopener noreffer ">Azure Resource Manager template (ARM template) test toolkit</a> checks whether your template uses recommended practices. When your template isn&rsquo;t compliant with recommended practices, it returns a list of warnings with the suggested changes. By using the test toolkit, you can learn how to avoid common problems in template development.</p>
<p>The test toolkit provides a set of default tests. These tests are recommendations but not requirements. You can decide which tests are relevant to your goals and customize which tests are run.</p>
<p>The toolkit is a set of PowerShell scripts that can be run from a command in PowerShell or CLI.</p>
<h2 id="additional-reads">Additional reads</h2>
<ul>
<li><a href="https://medium.com/@mikakrief/test-your-azure-infrastructure-compliance-with-inspec-9ac9f47ffb88" target="_blank" rel="noopener noreffer ">Test your Azure infrastructure compliance with Inspec</a></li>
<li><a href="https://blog.gruntwork.io/5-lessons-learned-from-writing-over-300-000-lines-of-infrastructure-code-36ba7fadeac1" target="_blank" rel="noopener noreffer ">5 Lessons Learned From Writing Over 300,000 Lines of Infrastructure Code</a></li>
<li><a href="https://blog.gruntwork.io/open-sourcing-terratest-a-swiss-army-knife-for-testing-infrastructure-code-5d883336fcd5" target="_blank" rel="noopener noreffer ">Terratest: a swiss army knife for testing infrastructure code</a></li>
<li><a href="https://devkimchi.com/2018/01/22/testing-arm-templates-with-pester/" target="_blank" rel="noopener noreffer ">Testing ARM Templates with Pester</a></li>
</ul>
]]></description></item><item><title>Cloud-native development with containers and microservices</title><link>https://clemens.ms/cloud-native-development/</link><pubDate>Thu, 18 Mar 2021 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/cloud-native-development/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><h2 id="introduction">Introduction</h2>
<p>To adopt <a href="https://www.cncf.io/" target="_blank" rel="noopener noreffer ">cloud-native</a> application patterns, companies must learn new skills and communicate effectively across team boundaries. A new architecture, platforms, and developer workflows mean evolving developer skillsets. While microservices allow teams to move independently, they also emphasize the degree to which software and the organization grow to resemble one another, also known as Conway&rsquo;s law. To achieve a result in which many microservices are a cohesive system and still maintain velocity, an organization&rsquo;s communication structures need to be highly functional and effective.</p>
<h3 id="initial-challenges">Initial Challenges</h3>
<h4 id="skillset">Skillset</h4>
<p>Development teams that are not primarily digital native, these development teams often do not have the incentive structure, time, experience, or knowledge to effectively adopt the design and architecture skills required to build cloud-native applications.</p>
<h4 id="timebudget">Time/Budget</h4>
<p>Another factor is just (too much) time pressure to build new features, and having a &ldquo;legacy&rdquo; application that works today may in many cases be &ldquo;good enough&rdquo; but continue to create a lot of the operational and productivity barriers that have given rise to cloud-native practices.</p>
<h4 id="development-infrastructure">Development Infrastructure</h4>
<p>Many development teams also lack the investment in a development infrastructure that is required to lean further into cloud-native: CI/CD pipelines, integration tests, monitoring and observability frameworks, cloud-based dev- and test environments. The cost of implementing all these pieces is a barrier to adopting a more cloud-native approach.</p>
<h4 id="organizational-structure">Organizational Structure</h4>
<p>Traditionally company communication structures stand in the way of cloud-native development. For example, all networking operations and deployments belong to a separate team from that which does development or Ops, creating barriers or synchronous process points that constrain the adoption of new technology or patterns. Microservices are creating new importance on API contracts, which often become the boundary between teams.</p>
<h4 id="legacy-anchors">Legacy Anchors</h4>
<p>Lastly, there may be technology assets that just aren&rsquo;t going to change for one reason or another. For several reasons, teams may be anchored to a legacy technology choice for some time to come and bridge their applications into the future somehow.</p>
<h3 id="cloud-native-development">Cloud-native development</h3>
<p>Cloud-native is all about changing the way you think about constructing critical business systems. Cloud-native systems are designed to embrace rapid change, large scale, and resilience. Cloud-native apps share some common traits: they are <strong>containerized</strong>, <strong>dynamically orchestrated</strong>, and composed of <strong>loosely coupled microservices</strong>. The cloud-native philosophy builds on top of established cloud application best-practices such as containers for application packaging, service discovery, 12 factors, dev-prod parity, centralized configuration, distributed tracing, multilingual applications, and fast, iterative development.</p>
<p>Cloud-native is about <strong>speed</strong> and <strong>agility</strong>. Business systems are evolving from enabling business capabilities to being weapons of strategic transformation that accelerate business velocity and growth. It&rsquo;s imperative to get ideas to market immediately.</p>
<p>The speed and agility of cloud-native come about from several factors. These five foundational pillars also provide the bedrock for cloud-native systems:</p>
<h2 id="the-twelve-factor-application">The Twelve-Factor Application</h2>
<p>A widely accepted methodology for constructing cloud-based applications is the Twelve-Factor Application. It describes principles and practices that developers follow to build applications optimized for modern cloud environments.</p>
<p>While applicable to any web-based application, many practitioners consider Twelve-Factor a solid foundation for building cloud-native apps. Systems built upon these principles can deploy and scale rapidly and add features to react quickly to market changes.</p>
<p>The following table highlights the Twelve-Factor methodology:</p>
<table>
  <thead>
      <tr>
          <th></th>
          <th>Factor</th>
          <th>Explanation</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1</td>
          <td>Code Base</td>
          <td>A single code base for each microservice stored in its own repository. Tracked with <a href="https://azure.microsoft.com/en-us/services/devops/repos/" target="_blank" rel="noopener noreffer ">version control</a>, it can deploy to multiple environments (QA, Staging, Production).</td>
      </tr>
      <tr>
          <td>2</td>
          <td>Dependencies</td>
          <td>Each microservice isolates and packages its <a href="https://docs.microsoft.com/en-us/azure/devops/extensions/dependency-tracker/overview" target="_blank" rel="noopener noreffer ">dependencies</a>, embracing changes without impacting the entire system.</td>
      </tr>
      <tr>
          <td>3</td>
          <td>Configurations</td>
          <td>Configuration information is moved out of the microservice and externalized through a <a href="https://azure.microsoft.com/en-us/services/app-configuration/" target="_blank" rel="noopener noreffer ">configuration management</a> tool outside of the code. The same deployment can propagate across environments with the correct configuration applied.</td>
      </tr>
      <tr>
          <td>4</td>
          <td>Backing Services</td>
          <td>Ancillary resources (<a href="https://docs.microsoft.com/en-us/azure/architecture/guide/technology-choices/data-store-overview" target="_blank" rel="noopener noreffer ">data stores, caches, message brokers</a>) should be exposed via an addressable URL. Doing so decouples the resource from the application, enabling it to be interchangeable.</td>
      </tr>
      <tr>
          <td>5</td>
          <td>Build, Release, Run</td>
          <td>Each release must enforce a strict separation across the build, release, and run stages. Each should be tagged with a unique ID and support the ability to roll back. Modern <a href="https://azure.microsoft.com/en-us/services/devops/pipelines/" target="_blank" rel="noopener noreffer ">CI/CD</a> systems help fulfill this principle.</td>
      </tr>
      <tr>
          <td>6</td>
          <td>Processes</td>
          <td>Each microservice should <a href="https://azure.microsoft.com/en-us/product-categories/containers/" target="_blank" rel="noopener noreffer ">execute in its own process</a>, isolated from other running services. Externalize required state to a backing service such as a distributed cache or data store.</td>
      </tr>
      <tr>
          <td>7</td>
          <td>Port Binding</td>
          <td>Each microservice should be self-contained with its interfaces and functionality exposed on its own port. Doing so provides isolation from other microservices.</td>
      </tr>
      <tr>
          <td>8</td>
          <td>Concurrency</td>
          <td>Services scale-out across a large number of small identical processes (copies) as opposed to scaling-up a single large instance on the most powerful machine available.</td>
      </tr>
      <tr>
          <td>9</td>
          <td>Disposability</td>
          <td>Service instances should be disposable, favoring fast startups to increase scalability opportunities and graceful shutdowns to leave the system in a correct state. Docker containers, along with an orchestrator (<a href="https://docs.microsoft.com/en-us/azure/aks/intro-kubernetes" target="_blank" rel="noopener noreffer ">Kubernetes</a>), inherently satisfy this requirement.</td>
      </tr>
      <tr>
          <td>10</td>
          <td>Dev/Prod Parity</td>
          <td>Keep <a href="https://docs.microsoft.com/en-us/azure/architecture/framework/devops/automation-infrastructure" target="_blank" rel="noopener noreffer ">environments</a> across the application lifecycle as similar as possible, avoiding costly shortcuts. Here, the adoption of containers can significantly contribute by promoting the same execution environment.</td>
      </tr>
      <tr>
          <td>11</td>
          <td>Logging</td>
          <td>Treat logs generated by microservices as event streams. Process them with an event aggregator and propagate the data to data-mining/log management tools like <a href="https://azure.microsoft.com/en-us/services/monitor/" target="_blank" rel="noopener noreffer ">Azure Monitor</a> or <a href="https://docs.microsoft.com/en-us/azure/azure-monitor/app/app-insights-overview" target="_blank" rel="noopener noreffer ">Azure Application Insights</a> and, eventually, long-term archival.</td>
      </tr>
      <tr>
          <td>12</td>
          <td>Admin Processes</td>
          <td>Run administrative/management tasks as one-off processes. Tasks can include data cleanup and pulling analytics for a report. Tools executing these tasks should be invoked from the production environment, but separately from the application.</td>
      </tr>
  </tbody>
</table>
<p>In the book, <a href="https://tanzu.vmware.com/content/ebooks/beyond-the-12-factor-app" target="_blank" rel="noopener noreffer ">Beyond the Twelve-Factor App</a>, the author <em>Kevin Hoffman</em> details each of the original 12 factors (written in 2011). Additionally, the author discusses three additional factors that reflect today&rsquo;s modern cloud application design.</p>
<table>
  <thead>
      <tr>
          <th></th>
          <th>New Factor</th>
          <th>Explanation</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>13</td>
          <td><a href="https://azure.microsoft.com/en-us/services/api-management/" target="_blank" rel="noopener noreffer ">API First</a></td>
          <td>Make everything a service. Assume your code will be consumed by a front-end client, gateway, or another service.</td>
      </tr>
      <tr>
          <td>14</td>
          <td>Telemetry</td>
          <td>On a workstation, you have deep visibility into your application and its behavior. In the Cloud, you don&rsquo;t. Make sure your design includes the collection of <a href="https://docs.microsoft.com/en-us/azure/azure-monitor/app/app-insights-overview" target="_blank" rel="noopener noreffer ">monitoring</a>, domain-specific, and health/system data.</td>
      </tr>
      <tr>
          <td>15</td>
          <td>Authentication/ Authorization</td>
          <td>Implement identity from the start. Consider RBAC (<a href="https://docs.microsoft.com/en-us/azure/role-based-access-control/overview" target="_blank" rel="noopener noreffer ">role-based access control</a>) features available in public clouds.</td>
      </tr>
  </tbody>
</table>
<h2 id="microservices">Microservices</h2>
<p>Cloud-native systems embrace microservices, a popular architectural style for constructing modern applications. Built as a distributed set of small, independent services that interact through a shared fabric, microservices share the following characteristics:</p>
<ul>
<li>Each implements a specific business capability within a larger domain context.</li>
<li>Each is developed autonomously and can be deployed independently.</li>
<li>Each is self-contained, encapsulating its own data storage technology (SQL, NoSQL) and programming platform.</li>
<li>Each runs in its own process and communicates with others using standard communication protocols.</li>
<li>They compose together to form an application.</li>
</ul>
<h3 id="why-microservices">Why microservices?</h3>
<p>Microservices provide agility. Each microservice has an autonomous lifecycle and can evolve independently and deploy frequently. You don&rsquo;t have to wait for a quarterly release to deploy new features or updates. You can update a small area of a complex application with less risk of disrupting the entire system. Each microservice can scale independently. Instead of scaling the entire application as a single unit, you scale out only those services that require more processing power or network bandwidth. This fine-grained approach to scaling provides for greater control of your system and reduces overall costs as you scale portions of your system, not everything.</p>
<h2 id="automation">Automation</h2>
<p>Cloud-native systems embrace microservices, containers, and modern system design to achieve speed and agility. But that&rsquo;s only part of the story. How do you provision the cloud environments upon which these systems run? How do you rapidly deploy app features and updates? How do you round out the complete picture?</p>
<p>Enter the widely accepted practice of <a href="https://docs.microsoft.com/en-us/dotnet/architecture/cloud-native/infrastructure-as-code" target="_blank" rel="noopener noreffer ">Infrastructure as Code</a>, or IaC.</p>
<p>With IaC, you automate platform provisioning and application deployment. You essentially apply software engineering practices such as testing and versioning to your DevOps practices. Your infrastructure and deployments are automated, consistent, and repeatable.</p>
<h3 id="automating-infrastructure">Automating infrastructure</h3>
<p>Tools like Azure Resource Manager, Terraform, Azure Bicep, and the Azure CLI, enable you to declaratively script the cloud infrastructure you require. Resource names, locations, capacities, and secrets are parameterized and dynamic. The script is versioned and checked into source control as an artifact of your project. You invoke the script to provide a consistent and repeatable infrastructure across system environments, such as QA, staging, and production.</p>
<p>IaC is idempotent under the hood, meaning that you can run the same script repeatedly without side effects. If the team needs to make a change, they edit and rerun the script. Only the updated resources are affected.</p>
<p>In the article, <a href="https://docs.microsoft.com/en-us/azure/devops/learn/what-is-infrastructure-as-code" target="_blank" rel="noopener noreffer ">What is Infrastructure as Code</a>, the author <em>Sam Guckenheimer</em> describes how &ldquo;Teams who implement IaC can deliver stable environments rapidly and at scale.&rdquo; Teams avoid manual configuration of environments and enforce consistency by representing the desired state of their environments via code.</p>
<p>Infrastructure deployments with IaC are <strong>repeatable</strong> and prevent runtime issues caused by configuration drift or missing dependencies. DevOps teams can work together with a unified set of practices and tools to deliver applications and their supporting infrastructure rapidly, reliably, and at scale.&quot;</p>
<h3 id="automating-deployments">Automating deployments</h3>
<p>The Twelve-Factor Application calls for separate steps when transforming completed code into a running application.</p>
<blockquote>
<p>Factor #5 specifies that &ldquo;Each release must enforce a strict separation across the build, release and run stages. Each should be tagged with a unique ID and support the ability to roll back.&rdquo;</p></blockquote>
<p>Modern CI/CD systems help fulfill this principle. They provide separate deployment steps and help ensure consistent and quality code that&rsquo;s readily available to users.</p>
<p>In the previous figure, pay special attention to the separation of tasks.</p>
<p>The developer constructs a feature in their development environment, iterating through what is called the &ldquo;inner loop&rdquo; of code, run, and debug. When complete, that code is pushed into a code repository, such as GitHub or Azure DevOps.</p>
<p>The push triggers a build stage that transforms the code into a binary artifact. The work is implemented with a Continuous Integration (CI) pipeline. It automatically builds, tests, and packages the application.</p>
<p>The release stage picks up the binary artifact, applies external application and environment configuration information, and produces an immutable release. The release is deployed to a specified environment. The work is implemented with a Continuous Delivery (CD) pipeline. Each release should be identifiable. You can say, &ldquo;This deployment is running Release 3.11 of the application.&rdquo;</p>
<p>Finally, the released feature is run in the target execution environment. Releases are immutable, meaning that <strong>any change must create a new release</strong>.</p>
<p>Applying these practices, you have radically evolved how to ship software. Many development teams have moved from quarterly releases to on-demand updates. The goal is to catch problems early in the development cycle when they&rsquo;re less expensive to fix. The longer the duration between integrations, the more expensive issues become to resolve. With consistency in the integration process, teams can commit code changes more frequently, leading to better collaboration and software quality.</p>
<h2 id="programming-frameworks">Programming frameworks</h2>
<h3 id="spring">Spring</h3>
<p><a href="https://azure.microsoft.com/en-us/services/spring-cloud/" target="_blank" rel="noopener noreffer ">Spring</a> is a trendy framework for microservices development and has rapidly evolved to support cloud application patterns. More than 50% of Enterprise applications are in Java, and more than 50% of Java applications use Spring, which has dedicated support on Azure</p>
<h3 id="dapr">Dapr</h3>
<p><a href="https://dapr.io/" target="_blank" rel="noopener noreffer ">Dapr</a> is a new framework that adds cloud-native capabilities and patterns to other popular language frameworks like Node.js, Python, Java, and .Net/C#. Dapr especially shines for interservice communication between microservices and state stores. For example, Dapr adds a side car pattern that adds resiliency, retries, and service discovery between HTTP and gRPC based microservices without having to fully implement a service mesh or write lower-level logic to handle. Dapr adds high scale and availability bindings to state stores and messaging services like CosmosDB, Redis, or Kafka.</p>
]]></description></item><item><title>Zero Touch Deployment</title><link>https://clemens.ms/zero-touch-deployment/</link><pubDate>Tue, 16 Mar 2021 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/zero-touch-deployment/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><h2 id="introduction">Introduction</h2>
<p>In today&rsquo;s increasingly demanding marketplace, companies are looking for ways to stay ahead of their competition. Development teams are faced with challenges preventing them from delivering solutions rapidly that meet the business&rsquo;s expectations. Therefore, companies need to evolve their <strong>Application Lifecycle Management</strong> (ALM) practices to integrate business, development, QA, and operations functions in an efficient cycle for greater agility in delivering continuous value.</p>
<p>Releasing high-quality software more frequently represents a substantial competitive advantage, and it means a significant reduction in the amount of wasted time and effort within a company.</p>
<h2 id="application-lifecycle-management">Application Lifecycle Management</h2>
<p>A modern application life cycle helps empower teams to support a continuous delivery cadence that balances agility and quality while removing the traditional silos separating developers from operations and business stakeholders, improving communication and collaboration within development teams, and driving connections between applications and business outcomes.</p>
<p><a href="https://docs.microsoft.com/en-us/azure/devops/user-guide/devops-alm-overview?view=azure-devops" target="_blank" rel="noopener noreffer ">Modern ALM</a> entails planning, developing, releasing, and operating software. These steps repeat to become <strong>Continuous Value Delivery</strong>. Within the cycle of Continuous Value Delivery, we build software, measure its effectiveness for its intended goal, learn from its observed behavior, improve the software, and sharpen the business goals associated with it or the development process.</p>
<p>Modern ALM consists of four high-level phases:</p>
<ol>
<li><strong>Plan</strong> It starts with defining the business hypothesis, specifying its work items on the backlog in the planning phase. Criteria specify how the business value will be measured.</li>
<li><strong>Develop</strong> Constructing and testing the software artifacts for delivering on the hypothesis during development. Metrics are implemented to measure value.</li>
<li><strong>Release</strong> Continuously releasing incremental versions of the working software assets into production. Staged deployments are used to release new features to the end-users gradually.</li>
<li><strong>Operate</strong> Capturing the learnings during the operating phase of the lifecycle</li>
</ol>
<p>From both a team and artifact perspective, it is critical to work together across all stages of the lifecycle, continuously learning from previous deliveries by measuring application behavior, team, and delivery process performance.</p>
<h2 id="devops">DevOps</h2>
<p>DevOps is the union of <strong>people</strong>, <strong>processes</strong>, and <strong>products</strong> to enable continuous delivery of value to your end-users.</p>
<p><a href="https://azure.microsoft.com/en-us/overview/what-is-devops/" target="_blank" rel="noopener noreffer ">DevOps</a> enables formerly siloed roles (development, IT operations, quality engineering, and security) to coordinate and collaborate to produce better, more reliable products. By adopting a DevOps culture along with DevOps practices and tools, teams gain a common reference for a status of deliverables, the ability to better respond to customer needs, increased confidence in the applications they build, enjoy better, more accurate communication on project status, and achieve business goals faster.</p>
<h3 id="continuous-integration-ci">Continuous Integration (CI)</h3>
<p><a href="https://docs.github.com/en/enterprise-server@3.0/actions/guides/about-continuous-integration" target="_blank" rel="noopener noreffer ">Continuous Integration</a> aims to prevent developers from stepping over each other&rsquo;s code and eliminating integration issues. Continuous Integration is a practice, <em>not a tool</em>. It requires a degree of commitment and discipline from your development team and increases awareness of who is doing what and the final product&rsquo;s quality.</p>
<h4 id="why-continuous-integration">Why Continuous Integration</h4>
<ul>
<li>Harness collaboration</li>
<li>Enable parallel development</li>
<li>Minimize integration debt</li>
<li>Act as a quality gate</li>
<li>Automate Everything!</li>
</ul>
<h4 id="how-continuous-integration">How Continuous Integration</h4>
<ul>
<li>Are all the developers on the team checking into the trunk (also called Main branch) at least once a day? In other words, are they doing <strong>trunk-based development</strong> and working in small batches?</li>
<li>Does every change to trunk kick off a <strong>build process</strong>, including running a set of automated tests to detect regressions?</li>
<li>When the build and test process fails, does the <strong>team fix the build</strong> within a few minutes, either by fixing the breakage or reverting the change that caused the build to break?</li>
</ul>
<p><strong>Version Control System (Git)</strong></p>
<ul>
<li>Application / Test Code</li>
<li>Configuration</li>
<li>Database Definition</li>
<li>Seed / Test Data</li>
<li>Build / Deployment Scripts</li>
<li>Infrastructure as code (IaC)</li>
</ul>
<p><strong>Branching Strategy</strong></p>
<ul>
<li>Have a <a href="https://docs.microsoft.com/en-us/azure/devops/repos/git/git-branching-guidance?view=azure-devops" target="_blank" rel="noopener noreffer ">branching strategy</a></li>
<li>Keep changes small</li>
<li>Commit frequently</li>
<li>At least once per day for each developer</li>
</ul>
<p><strong>Automated Build</strong></p>
<ul>
<li>Runs after every commit/check-in</li>
<li>Checks quality of the commit (Shift-Left Testing)</li>
<li>Separate Build machine (Build Server)</li>
<li>Must run fast</li>
</ul>
<h3 id="continuous-delivery-cd">Continuous Delivery (CD)</h3>
<p>Continuous Delivery is a software engineering approach in which teams produce software in short cycles, ensuring that the software can be reliably released at any time and, when releasing the software, doing so manually. It aims at building, testing, and releasing software with greater speed and frequency. The approach helps reduce the cost, time, and risk of delivering changes by allowing for more incremental updates to applications in production. A straightforward and repeatable deployment process is essential for continuous delivery.</p>
<p><strong>Continuous Deployment</strong> (CD) is a software development method that releases or deploys software automatically into the production environment multiple times a day.</p>
<h4 id="why-continuous-delivery">Why Continuous Delivery</h4>
<ul>
<li>Quicker delivery of business value</li>
<li>Faster business feedback</li>
<li>Reduced risk to project schedules</li>
<li>Less time spent supporting production deployment stabilization = increased time developing new features</li>
<li>Increased confidence in the production readiness of the software</li>
<li>More production-like test environments</li>
<li>Error-free production deployments (no manual steps)</li>
</ul>
<h4 id="how-continuous-delivery">How Continuous Delivery</h4>
<ul>
<li>Deployments are scripted or automated via an Automated Deployment tool.</li>
<li>Automation of Build, test, deployment, database migration, and infrastructure (IaC)</li>
<li>Automated Deployments are configurable, repeatable, and reliable</li>
<li>Deployment automation artifacts are stored and maintained in the source control system, ensuring they evolve along with the application</li>
<li>Use the same process for all deployments to all environments</li>
<li>The automated deployment process includes automated smoke testing</li>
<li>Practice CI, Configuration Management, and Automated Testing</li>
<li>Involve everyone involved in delivery to work together throughout the software delivery lifecycle</li>
</ul>
<blockquote>
<p><strong>Key Takeaway</strong> To be successful in implementing continuous delivery, you have to act as a team. Trust and cooperation are the two main ingredients for success. You can start small, but cooperation with the rest of the organization is required to make it a success, and you will need to get everybody on board.</p></blockquote>
<h3 id="continuous-security">Continuous Security</h3>
<p>As the frequency of (production) deployments increases, this business agility cannot come at the expense of security. This begins with practices commonly referred to as <a href="https://docs.microsoft.com/en-us/azure/devops/migrate/security-validation-cicd-pipeline" target="_blank" rel="noopener noreffer ">DevSecOps</a>. DevSecOps incorporates the security team and their capabilities into your DevOps practices making security a responsibility of everyone on the team.</p>
<h4 id="why-continuous-security">Why Continuous Security</h4>
<p>Security needs to shift from an afterthought to being evaluated at every step of the process. Securing applications is a continuous process that encompasses secure infrastructure, designing an architecture with layered security, continuous security validation, and monitoring attacks. This approach aims to switch the conversation with the security team from approving each release to supporting the CI/CD process and monitoring and auditing the process at any time.</p>
<p>Azure Repos source control in <a href="https://azure.microsoft.com/en-us/solutions/devops/" target="_blank" rel="noopener noreffer ">Azure DevOps</a>, together with <strong>branch policies</strong>, provides a gated commit experience that can give this validation, only allowing code to be deployed after it clears security (which can be anything from static code analysis to security-specific test).</p>
<p>A <strong>threat model</strong> needs to be established to verify if technical implementation mitigated identified risks. Use tools and automation to identify and mitigate potential security issues early in the development process and review that threat model as development progresses – making security an integrated, continuous practice that goes hand in hand with development.</p>
<p>The practices used in DevOps provide an excellent opportunity to improve security. Practices such as automation, monitoring, collaboration, and fast and early feedback provide a great foundation to build security into DevOps processes.</p>
<p>Fast and early feedback can be achieved to have a CI-build run static code analysis tests to ensure that the code follows all maintenance and security rules.</p>
<p>High-level, &ldquo;overview&rdquo; tools like <strong>Azure Security Center</strong> provide DevOps teams end-to-end insights into the security posture of their (hybrid) infrastructure. It is a unified infrastructure security management system that strengthens your datacenters&rsquo; security posture and provides advanced threat protection across your hybrid workloads in the cloud - whether they&rsquo;re in Azure or not - as well as on-premises. Low-level, &ldquo;detail&rdquo; tools like cloud-native monitoring solutions such as <strong>Azure Monitor</strong> and <strong>Application Insights</strong> enable end-to-end monitoring capabilities to DevOps teams from the Infrastructure to Application level. They can act as tripwires/sensors to provide alerting and auditing data for security assessments.</p>
<p>However, these tools are not complete without considering the implications of user/developer identity and access control. To mitigate the risks of excessive, unnecessary, or misused access permissions on resources <strong>Azure Active Directory</strong> (AD) <strong>Privileged Identity Management</strong> (PIM) should be used. When CI/CD requires the use of non-personal (or &ldquo;service&rdquo;) accounts, proper governance needs to be implemented. Azure DevOps uses service connections to connect to a Microsoft Azure subscription. A service connection uses a service principal (cloud-native service account) in an Azure AD tenant. Service connections and their service principal can be programmatically created and configured during the creation of the Azure DevOps project.</p>
<p>When deploying or accessing Azure resources, a secure, policy-friendly alternative are <strong>Managed Identities</strong>, which can address several use cases:</p>
<ul>
<li><a href="https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview" target="_blank" rel="noopener noreffer ">What are managed identities for Azure resources</a></li>
<li><a href="https://www.microsoft.com/security/blog/2021/02/08/why-threat-protection-is-critical-to-your-zero-trust-security-strategy/" target="_blank" rel="noopener noreffer ">Why threat protection is critical to your Zero Trust security strategy</a></li>
</ul>
<h4 id="how-continuous-security">How Continuous Security</h4>
<p>Implementing secure DevOps together with <a href="https://azure.microsoft.com/en-us/services/kubernetes-service/" target="_blank" rel="noopener noreffer ">Kubernetes on Azure</a>, you can achieve the balance between speed and security and deliver code faster at scale. Put guardrails around the development processes using CI/CD with dynamic policy controls and accelerate feedback loop with constant monitoring. Use Azure Pipelines to deliver fast while ensuring enforcement of critical policies with Azure Policy. Azure provides you real-time observability for your build and release pipelines and the ability to apply compliance audit and reconfigurations easily.</p>
<ol>
<li>Developers rapidly iterate, test, and debug different parts of an application together in the same Kubernetes cluster.</li>
<li>Code is merged into a GitHub repository, after which Azure Pipelines run automated builds and tests.</li>
<li>Release pipeline automatically executes pre-defined deployment strategy with each code change.</li>
<li>Kubernetes clusters are provisioned using Helm charts that define the desired state of app resources and configurations.</li>
<li>Container image is pushed to Azure Container Registry.</li>
<li>Cluster operators define policies in Azure Policy to govern deployments to the AKS cluster and can leverage Active Directory Role-Based Access Control for fine-grained permissions.</li>
<li>Azure Policy audits requests from the pipeline at the AKS control plane level.</li>
<li>App telemetry, container health monitoring, and real-time log analytics are obtained using Azure Monitor.</li>
<li>Insights are used to address issues and fed into next sprint plans.</li>
</ol>
<p>Control access to cluster resources using Kubernetes <a href="https://docs.microsoft.com/en-us/azure/aks/azure-ad-rbac" target="_blank" rel="noopener noreffer ">role-based access control</a> and Azure Active Directory identities in Azure Kubernetes Service.</p>
<h2 id="recommendations">Recommendations</h2>
<ol>
<li><strong>Standardize agile tools to support modern processes such as scrum and agile.</strong> Azure Boards supports end-to-end traceability and deep integration between Boards, Repositories, and Pipelines.</li>
<li><strong>DevOps teams treat infrastructure as code and application code as equals.</strong> Infrastructure as code and application code are stored in source control, with branch policies, and build and deployed with continuous integration continuous delivery pipelines. Use Azure Repos and Pipelines for both infrastructure as code and application code.</li>
<li><strong>Security is the responsibility of everyone in the DevOps team.</strong> Security needs to shift from an afterthought to being evaluated at every step of the process using preapproved Security Frameworks and building blocks.</li>
<li><strong>Adopt Inner Source to enable collaboration and knowledge sharing.</strong> Practicing InnerSource can significantly add value to an organization&rsquo;s development approach. Universal access to development code and documentation allows anyone to inspect and submit contributions.</li>
<li><strong>Adopt shift-left testing to fasten the feedback loop.</strong> Automate testing and validate deliverables as early as possible in the process. All tests are automated—no manual actions are required for deployment to production.</li>
<li><strong>Provide autonomy to DevOps teams.</strong> Autonomy for DevOps teams facilitates an Agile way of working because a DevOps team can deploy infrastructure and application code without IT becoming a man-in-the-middle. This autonomy leads to better security and operations because a DevOps team has end-to-end visibility over the whole landscape.</li>
</ol>
]]></description></item><item><title>Azure Key Vault</title><link>https://clemens.ms/azure-key-vault/</link><pubDate>Mon, 15 Mar 2021 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/azure-key-vault/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.png" referrerpolicy="no-referrer">
            </div><h2 id="introduction">Introduction</h2>
<p>Azure Key Vault is a managed service that offers enhanced protection and control over secrets and keys used by applications, running both in Azure and on-premises.</p>
<h2 id="the-basics">The Basics</h2>
<h3 id="service-tiers">Service Tiers</h3>
<p>Azure Key Vault is currently offered in two service tiers: Standard and Premium. Key Vault in Standard tier is limited to secrets and software-protected keys, while Key Vault in Premium tier additionally supports keys stored in <a href="https://azure.microsoft.com/en-us/updates/akv-managed-hsm-public-preview/" target="_blank" rel="noopener noreffer ">Hardware Security Modules</a> (HSMs) and are FIPS 140-2 Level 3 validated.</p>
<p>Managed HSM is a new resource type under Azure Key Vault that allows you to store and manage HSM-keys for your cloud applications using the same Key Vault APIs, which means migrating from vaults to managed HSM pools should be very simple.</p>
<p>Service tier must be specified at Key Vault creation time and cannot be changed after Key Vault has been created.</p>
<p>To create Key Vault in Standard tier:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-powershell">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="nb">New-AzureRmKeyVault</span> <span class="n">-ResourceGroupName</span> <span class="n">NavaTronRG</span>
</span></span><span class="line"><span class="cl">                    <span class="n">-VaultName</span> <span class="n">NavaTronSoftHSM</span>
</span></span><span class="line"><span class="cl">                    <span class="n">-Location</span> <span class="s2">&#34;West US&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="n">-SKU</span> <span class="n">standard</span></span></span></code></pre></div></div>
<p>To create Key Vault in Premium tier:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-powershell">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="nb">New-AzureRmKeyVault</span> <span class="n">-ResourceGroupName</span> <span class="n">NavaTronRG</span>
</span></span><span class="line"><span class="cl">                    <span class="n">-VaultName</span> <span class="n">NavaTronHSM</span>
</span></span><span class="line"><span class="cl">                    <span class="n">-Location</span> <span class="s2">&#34;West US&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="n">-SKU</span> <span class="n">premium</span></span></span></code></pre></div></div>
<blockquote>
<p><strong>Warning</strong> The <code>New-AzureRmKeyVault</code> cmdlet creates a Key Vault and grants the current user (one who invoked the cmdlet) <strong>full access rights</strong> to this newly created Key Vault and all future objects in it.</p></blockquote>
<blockquote>
<p><strong>Information</strong> The <code>New-AzureRmKeyVault</code> cmdlet requires you to provide both resource group name and location for the Key Vault being created. The Key Vault location <strong>does not</strong> need to match the location of the resource group in which it is being created. You can have a single resource group in any location contain multiple Key Vaults from multiple locations.</p></blockquote>
<h3 id="object-types">Object Types</h3>
<p>Azure Key Vault provides native support for <a href="https://docs.microsoft.com/en-us/azure/key-vault/general/about-keys-secrets-certificates" target="_blank" rel="noopener noreffer ">three object types</a>: <strong>secrets</strong>, <strong>keys</strong>, and <strong>certificates</strong>. Each object may have an unlimited number of versions.</p>
<h4 id="secrets">Secrets</h4>
<p>Secret is an opaque binary blob of up to 25 kilobytes in size. Key Vault provides no semantics for secrets; it merely provides a way for an application to store and retrieve secrets. Applications would typically store connection strings, passwords, symmetric encryption keys, and certificates as secrets in Key Vault.</p>
<p>Key Vault supports a contentType attribute that that may be used to help client application to interpret the value of the secret. This attribute can be up to 255 characters long.</p>
<p>Key Vault also supports several other attributes for secrets and different object types; see <a href="#object-attributes" rel="">Object Attributes</a> for more details.</p>
<h4 id="keys">Keys</h4>
<p>Key is an RSA cryptographic key (Key Vault also supports Elliptic Curve keys). Once created or imported, keys can be used to perform cryptographic operations (such as signing or decrypting data). Still, they cannot be exported back (except in protected form for backup purposes).</p>
<p>Key Vault supports two kinds of <a href="https://docs.microsoft.com/en-us/azure/key-vault/keys/about-keys" target="_blank" rel="noopener noreffer ">keys</a>:</p>
<ul>
<li>&ldquo;Software&rdquo; (or &ldquo;soft&rdquo;) keys, which are processed in software by Key Vault but are encrypted at rest using a true HSM key. &ldquo;Soft&rdquo; keys can be generated by Key Vault or imported from an existing PFX file.</li>
<li>HSM keys, which are processed by a FIPS 140-2 compliant HSM. Keys can be generated by HSM, imported from PFX file, or imported from a compatible on-premise Thales HSM. HSM keys are only available for Key Vaults in a Premium tier.</li>
</ul>
<h4 id="certificates">Certificates</h4>
<p>Certificate is a virtual and compound object that encapsulates X.509 certificate and corresponding private key. The certificate object ties together a PFX or PEM container containing the certificate&rsquo;s private key (stored as a secret) and certificate&rsquo;s private key stored as a key and available for cryptographic operations.</p>
<p>Certificate object in Key Vault is associated with <strong>certificate policy</strong> and <strong>certificate issuer</strong>. Certificate policy contains information on how to create and manage the lifecycle of a certificate. It includes the following:</p>
<ul>
<li>Certificate properties (subject name, alternative names)</li>
<li>Key properties (type, length)</li>
<li>Secret properties (content type)</li>
<li>Renew actions</li>
<li>Issuer</li>
</ul>
<p>A certificate issuer is an object containing information to communicate with the certificate authority to issue or renew a certificate.</p>
<h3 id="object-attributes">Object Attributes</h3>
<p>Key Vault supports the following attributes for secrets, keys, and certificates:</p>
<table>
  <thead>
      <tr>
          <th>Attribute Name</th>
          <th>Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>nbf, exp</td>
          <td>&ldquo;Not before&rdquo; and &ldquo;not after&rdquo; (expiration) dates. Key Vault will not allow retrieval of the secret or perform a cryptographic operation using the key if the current date and time fall outside of those values (that is, before <code>nbf</code> or after <code>exp</code>).</td>
      </tr>
      <tr>
          <td>enabled</td>
          <td>A flag indicating whether Key Vault should allow retrieval of the secret or performing a cryptographic operation using the key. Note that operations outside of <code>nbf</code>/<code>exp</code> windows are always forbidden regardless of value of this flag.</td>
      </tr>
  </tbody>
</table>
<p>An application may associate up to 15 tags (key-value pairs) with each secret and key in addition to the above-predefined attributes. Both name (key) and value of each tag may be up to 256 characters.</p>
<h3 id="addressing-and-versioning">Addressing and Versioning</h3>
<p>Key Vault objects can be referenced using the following URI format:</p>
<p><code>https://{vault-name}.vault.azure.net/{object-type}/{object-name}/{object-version}</code></p>
<p>Where:</p>
<ul>
<li><strong>vault-name</strong> — name of a Key Vault. Provided by the user at Key Vault creation time, must be a globally unique string of 3-24 characters containing only (0-9, a-z, A-Z, -)</li>
<li><strong>object-type</strong> — type of the object being accessed, must be either secrets, keys, or certificates</li>
<li><strong>object-name</strong> — name of an object. Provided by the user at the object creation time, must be a locally unique string of 1-127 characters containing only (0-9, a-z, A-Z, -)</li>
<li><strong>object-version</strong> — optional identifier of an object version. Generated by the Key Vault for each version and is a 32-character hexadecimal string. If omitted, the most recent version of the object specified by &lsquo;object-name&rsquo; is used.</li>
</ul>
<h3 id="permissions-and-access-policies">Permissions and Access Policies</h3>
<p>With Key Vault, permissions can be controlled at Key Vault instance and at object type level. It is possible to grant a user, a group, or application a permission (or set of permissions) for all keys, or all secrets, or all certificates within a particular Key Vault instance. It is not possible to grant different sets of permissions for different keys, or different secrets, or different certificates within the same Key Vault instance. In other words, Key Vault does not support per-object access control.</p>
<blockquote>
<p><strong>Information</strong> It is important to remember that whenever Key Vault is created using <code>New-AzureRmKeyVault</code> cmdlet, the user that created it gets full access to keys, secrets, and certificates. Therefore, for production workloads, it is necessary to revoke or at least severely limit that access.</p></blockquote>
<p>Key Vault uses Azure Active Directory for authentication and authorization needs. Access to Key Vault objects can be granted, changed, and removed using <code>Set-AzureRmKeyVaultAccessPolicy</code> and <code>Remove-AzureRmKeyVaultAccessPolicy</code> cmdlets.</p>
<p>The following example grants particular user permissions to list secrets and create new keys in a particular Key Vault instance:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-powershell">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="nb">Set-AzureRmKeyVaultAccessPolicy</span> <span class="n">-VaultName</span> <span class="n">NavaTronHSM</span>
</span></span><span class="line"><span class="cl">                                <span class="n">-UserPrincipalName</span> <span class="n">user</span><span class="nv">@navatron</span><span class="p">.</span><span class="py">com</span>
</span></span><span class="line"><span class="cl">                                <span class="n">-PermissionsToSecrets</span> <span class="n">list</span>
</span></span><span class="line"><span class="cl">                                <span class="n">-PermissionsToKeys</span> <span class="n">create</span></span></span></code></pre></div></div>
<p>There could be scenarios when access should be granted based on the user&rsquo;s group membership. The example below demonstrates how to grant permissions to all members of a group called &ldquo;NavaTronSecurityGroup&rdquo; to get secrets and perform all operations with certificates:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-powershell">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="nv">$group</span> <span class="p">=</span> <span class="nb">Get-AzureRmADGroup</span> <span class="n">-SearchString</span> <span class="s2">&#34;NavaTronSecurityGroup&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">Set-AzureRmKeyVaultAccessPolicy</span> <span class="n">-VaultName</span> <span class="n">NavaTronHSM</span>
</span></span><span class="line"><span class="cl">                                <span class="n">-ObjectId</span> <span class="nv">$group</span><span class="p">.</span><span class="py">Id</span>
</span></span><span class="line"><span class="cl">                                <span class="n">-PermissionsToKeys</span> <span class="n">get</span>
</span></span><span class="line"><span class="cl">                                <span class="n">-PermissionsToCertificates</span> <span class="n">all</span></span></span></code></pre></div></div>
<p>Lastly, perhaps the most important step is to enable applications and services to access Key Vault objects. The following example demonstrates how to grant application called &lsquo;NavaTronServiceApplication&rsquo; permissions to list secrets and to create new keys, backup existing keys, and compute digital signatures using existing keys:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-powershell">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="nv">$sp</span> <span class="p">=</span> <span class="nb">Get-AzureRmADServicePrincipal</span> <span class="n">-SearchString</span> <span class="s2">&#34;NavaTronServiceApplication&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">Set-AzureRmKeyVaultAccessPolicy</span> <span class="n">-VaultName</span> <span class="n">NavaTronHSM</span>
</span></span><span class="line"><span class="cl">                                <span class="n">-ServicePrincipalName</span> <span class="nv">$sp</span><span class="p">.</span><span class="py">ServicePrincipalName</span>
</span></span><span class="line"><span class="cl">                                <span class="n">-PermissionsToSecrets</span> <span class="n">list</span>
</span></span><span class="line"><span class="cl">                                <span class="n">-PermissionsToKeys</span> <span class="n">create</span><span class="p">,</span><span class="n">sign</span><span class="p">,</span><span class="n">backup</span></span></span></code></pre></div></div>
<p>There are two broad sets of PowerShell cmdlets to work with Key Vault:</p>
<p><code>[Action]-AzureKeyVault[Subject]</code> and <code>[Action]-AzureRmKeyVault[Subject]</code>. It is important to understand that access is controlled differently for those two sets of cmdlets.</p>
<p><code>AzureKeyVault</code> cmdlets perform actions using Key Vault REST API, and thus will be governed by permissions that user/principal has on the Key Vault object type (such as key, secret, or certificate) it tries to perform action on.</p>
<p>Contrary to that, <code>AzureRmKeyVault</code> cmdlets perform actions using Azure Resource Management API, and thus will be governed by the permissions that user/principal has on the subscription/resource group level. Because of this it is possible, for example, for a subscription co-administrator to gain access to all contents of any Key Vaults within the subscription: co-administrator has sufficient privileges to assign access policies on the Key Vault. High-assurance applications should consider creating Key Vaults in separate subscriptions with a limited number of administrators and stricter access controls.</p>
<h2 id="logging">Logging</h2>
<p>Azure Key Vault can be configured to write an <a href="https://docs.microsoft.com/en-us/azure/key-vault/general/logging" target="_blank" rel="noopener noreffer ">audit log</a> of authenticated operations performed. Access logs are in JSON format and will be stored as blobs in the target storage account. The storage account has to be in the same subscription as the Key Vault.</p>
<p>Example below demonstrates how audit logging with a retention period of 60 days can be enabled for the Key Vault:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-powershell">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="nv">$sa</span> <span class="p">=</span> <span class="nb">Get-AzureRmStorageAccount</span> <span class="n">-ResourceGroupName</span> <span class="n">NavaTronRG</span> <span class="n">-Name</span> <span class="n">NavaTronKvLogs</span>
</span></span><span class="line"><span class="cl"><span class="nv">$kv</span> <span class="p">=</span> <span class="nb">Get-AzureRmKeyVault</span> <span class="n">-VaultName</span> <span class="n">NavaTronSoftHSM</span>
</span></span><span class="line"><span class="cl"><span class="nb">Set-AzureRmDiagnosticSetting</span> <span class="n">-StorageAccountId</span> <span class="nv">$sa</span><span class="p">.</span><span class="py">Id</span>
</span></span><span class="line"><span class="cl">                             <span class="n">-ResourceId</span> <span class="nv">$kv</span><span class="p">.</span><span class="py">ResourceId</span>
</span></span><span class="line"><span class="cl">                             <span class="n">-Enabled</span> <span class="vm">$true</span>
</span></span><span class="line"><span class="cl">                             <span class="n">-RetentionEnabled</span> <span class="vm">$true</span>
</span></span><span class="line"><span class="cl">                             <span class="n">-RetentionInDays</span> <span class="mf">60</span></span></span></code></pre></div></div>
<p>Once logging is enabled, all authenticated requests (both successful and failed) will be written to the blob storage to the container named <code>insights-logs-auditevent</code>. There could be a delay of up to 10 minutes between the event and its appearance in the log.</p>
<p><strong><a href="https://techcommunity.microsoft.com/t5/azure-sentinel/visibility-of-azure-key-vault-activity-in-sentinel-azure-key/ba-p/2140751?_lrsc=968ca270-8ac3-40bf-9d87-e8c646ee204e" target="_blank" rel="noopener noreffer ">Azure Defender</a></strong> for Key Vault monitors key vault activity and alerts when suspicious and malicious behavior is observed.</p>
<h2 id="redundancy">Redundancy</h2>
<p>Azure Key Vault provides redundancy by using <strong>paired regions</strong>. In case the primary region is having issues, the service fails over to the secondary one. Service degrades to &ldquo;read-only&rdquo; mode: existing secrets and keys will continue to be accessible for operations (e.g., reading a secret or performing a cryptographic operation using a key) but attempts to create new objects or to modify existing ones will fail until Key Vault in the primary region is recovered.</p>
<h2 id="backup-and-restore">Backup and Restore</h2>
<p>Backup is a crucial part of Business Continuity and Disaster Recovery Planning, and Key Vault is not an exception. Secrets, keys, and certificates stored in the Key Vault <strong>should be backed up</strong>, and backups should be guarded closely.</p>
<p>Particular care should be taken when backing up keys. Key backup (no matter if &ldquo;software&rdquo; or true HSM) can be restored by the malicious user to the Key Vault under her control, and this will enable performing of any and all private key operations.</p>
<p>With Azure Key Vault, backup of a key can only be restored to the same geography from which it was originally backed up; backups and restores across different geographies are not possible.</p>
<p>Backup domains/geographies should be taken into account when planning application or service that needs to be deployed across multiple geographies. If such application requires high level of security assurance and opts in for Key Vault-generated keys (no matter if HSM or software), as opposed to keys generated elsewhere and imported into Key Vault, then there will be no way to replicate those keys across geographies because keys cannot be replicated across backup domains. This means that the application must be designed to support multiple keys for the operations it&rsquo;s doing. It must be designed to support multiple signers or multiple encryption keys or, more generally speaking, multiple roots of trust.</p>
<h2 id="scalability-and-service-limits">Scalability and Service Limits</h2>
<p>Compared to other Azure offerings, Key Vault comes with rather conservative service limits and no scale-out options. Applications must take those limitations into account. Please refer to the table below for current <a href="https://docs.microsoft.com/en-us/azure/key-vault/general/service-limits" target="_blank" rel="noopener noreffer ">Key Vault service limits</a>.</p>
<table>
  <thead>
      <tr>
          <th>Request Type</th>
          <th>Max. transactions per 10 seconds per Vault per region</th>
          <th>Max. transactions per 10 seconds per subscription</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Create Key (HSM)</td>
          <td>5</td>
          <td>25</td>
      </tr>
      <tr>
          <td>Create Key (Software)</td>
          <td>10</td>
          <td>50</td>
      </tr>
      <tr>
          <td>Other Key Operations (HSM)</td>
          <td>1.000</td>
          <td>5.000</td>
      </tr>
      <tr>
          <td>Other Key Operations (Software)</td>
          <td>1.500</td>
          <td>7.500</td>
      </tr>
      <tr>
          <td>Secret Operations</td>
          <td>2.000</td>
          <td>10.000</td>
      </tr>
  </tbody>
</table>
<p>From a practical point of view, those limitations wouldn&rsquo;t matter for the vast majority of the applications. If, however, the application reaches those limits, then it is possible to scale-out by adding more Key Vaults and adding necessary load balancing logic to the application because Key Vault does not provide this functionality out of the box.</p>
<h2 id="devops-pipelines">DevOps Pipelines</h2>
<p>Use Azure Key Vault secrets during deployments:</p>
<p><strong>Azure Pipelines</strong></p>
<ul>
<li><a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/release/azure-key-vault" target="_blank" rel="noopener noreffer ">Use Azure Key Vault secrets in Azure Pipelines</a></li>
<li><a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/deploy/azure-key-vault" target="_blank" rel="noopener noreffer ">Azure Key Vault task</a></li>
</ul>
<p><strong>Terraform</strong></p>
<ul>
<li><a href="https://blog.azureandbeyond.com/2019/01/29/terraform-azure-keyvault-secrets/" target="_blank" rel="noopener noreffer ">Use Azure KeyVault secrets during deployments</a></li>
</ul>
<h2 id="azure-app-configuration">Azure App Configuration</h2>
<p><a href="https://azure.microsoft.com/en-us/services/app-configuration/" target="_blank" rel="noopener noreffer ">App Configuration</a> is <strong>complementary to Key Vault</strong>. They&rsquo;re typically used side by side to store and distribute application configuration data. While Key Vault is designed for secret management and operations, App Configuration is optimized for hierarchical and dynamic application settings.</p>
<p>Store configuration for all your Azure apps in a universally hosted location. Manage configurations effectively and reliably, in real-time, without affecting customers by avoiding time-consuming redeployments. Azure App Configuration is built for speed, scalability, and security.</p>
<p>Toggle specific features behind feature flags, and fix critical problems in real-time. The flexibility provided by avoiding costly redeployments gives you more control when it matters the most.</p>
<h3 id="reduce-configuration-complexity-across-multiple-environments">Reduce configuration complexity across multiple environments</h3>
<p>Modern programs, especially those running in a cloud, have several distributed components. Spreading configuration settings across these components leads to hard-to-troubleshoot errors during application deployments. Using a universal configuration store for all your settings helps eliminate these errors.</p>
<p>Make your apps more secure by keeping data separate from code. Storing configuration settings in a hosted environment helps keep data secure.</p>
]]></description></item><item><title>WiFi Calling</title><link>https://clemens.ms/wifi-calling/</link><pubDate>Thu, 16 Jul 2020 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/wifi-calling/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><p>Modern buildings are well isolated, which isn&rsquo;t good if you are making a mobile phone call. Outside your house or office, the cellular signal is perfect, but inside it is another story, dropped calls, low-quality sound, etc. Even when your carrier has high coverage, there are situations inside buildings that prevent you from making a call.</p>
<p>Most mobile network providers (carriers) provide a solution for this, called <strong>WiFi Calling</strong>. By utilizing the existing WiFi network in your home or office, the mobile phone can make calls. The phone call is routed throw your Internet connection back to your carrier. Phone calls are not free and cost still money when WiFi Calling is enabled.</p>
<h2 id="wifi-calling-advantages">WiFi Calling advantages</h2>
<p>With WiFi Calling, you can make or receive a phone call if you have a WiFi connection in an area with little or no cellular coverage. When your carrier and smartphone support WiFi Calling, please enable it, it makes calling and texting in these modern buildings excellent again.</p>
<ul>
<li>Beter sound quality and stable connections.</li>
<li>Quicker to connect (1 or 2 sec.)</li>
<li>Simultaneously call and browse the Internet. (data connection remains intact.)</li>
<li>Your phone selects automatically if WiFi Calling is available.</li>
</ul>
<h2 id="how-to-enable-wifi-calling">How to enable WiFi Calling</h2>
<p>You can enable WiFi Calling on most modern smartphones from Apple or Samsung. For Apple iPhones, IOS 12.1 is the minimal version that supports WiFi Calling. Under <em>Settings</em> -&gt; <em>Phone</em> -&gt; <em>Wi-Fi Calling</em>.</p>
<p>If your <a href="https://support.apple.com/en-us/HT204039" target="_blank" rel="noopener noreffer ">carrier supports WiFi Calling on iCloud-connected devices</a>, you can also make and receive WiFi calls on other devices. How cool is that!</p>
]]></description></item><item><title>Free and Built-In TLS/SSL certificates in Azure</title><link>https://clemens.ms/free-and-built-in-tls-ssl-certificates-in-azure/</link><pubDate>Thu, 18 Jun 2020 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/free-and-built-in-tls-ssl-certificates-in-azure/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><p>Today, when a website does not have an <strong>SSL/TSL certificate</strong>, web browsers give you a warning <code>not secure</code>. This warning not only scares people but also gives you a disadvantage in search engine ranking. On Azure, web sites have a default https-enabled URL, like <code>https://sitename.azurewebsites.net/</code>, but when you have a vanity domain configured, you are missing this secure connection. Luckily there are some free SSL/TLS certificate options to explore.</p>
<h2 id="lets-encrypt">Let&rsquo;s Encrypt</h2>
<p>Wait, there is <a href="https://letsencrypt.org/" target="_blank" rel="noopener noreffer ">Let&rsquo;s Encrypt</a>, its free! Why are you not using this excellent service? Yes, that is true, but there are some downsides to use Let&rsquo;s Encrypt (on Azure), like:</p>
<ul>
<li>You need an Azure Web App Site Extension to enable and renew certificates, like this one from <a href="https://github.com/sjkp/letsencrypt-siteextension/" target="_blank" rel="noopener noreffer ">Simon J.K. Pedersen</a>.</li>
<li>Let&rsquo;s Encrypt does not have any official support or SLA model.</li>
<li>It&rsquo;s complex to implement correctly for all the Azure services you need.</li>
<li>Let&rsquo;s Encrypt only validates the domain name. There are <strong>no further validations</strong> like other CAs, and certificates are misused for phishing attacks.</li>
</ul>
<h2 id="azure-built-in-free-certificates">Azure Built-In free certificates</h2>
<p>It should be easy and free to enable SSL/TSL certificates in Azure. This was the number one question on <a href="https://feedback.azure.com/forums/170024-additional-services" target="_blank" rel="noopener noreffer ">UserVoice feedback</a>. Microsoft implemented this Built-In free certificate option for some services, like Azure <a href="https://azure.microsoft.com/en-us/services/cdn/" target="_blank" rel="noopener noreffer ">CDN</a>, <a href="https://azure.microsoft.com/en-us/services/frontdoor/" target="_blank" rel="noopener noreffer ">Front Door</a>, <a href="https://azure.microsoft.com/en-us/services/application-gateway/" target="_blank" rel="noopener noreffer ">Application Gateway</a>, etc. When you put one of those services in front of your web site (like what I did with <a href="/" rel="">this blog</a>), you can enable an auto-renewable Built-In certificate for free.</p>
<blockquote>
<p>&ldquo;<a href="https://docs.microsoft.com/en-us/azure/cdn/cdn-custom-ssl" target="_blank" rel="noopener noreffer ">Custom Domain HTTPS feature</a> enables you to deliver content to your users securely over your own domain. This is done by encrypting the data between the CDN and your users&rsquo; clients (typically web browsers) via TLS protocol (which is a successor of the SSL protocol) using a certificate. Using our &ldquo;CDN managed certificate&rdquo; capability, you can enable this feature with just a few clicks and have Azure CDN completely take care of certificate management tasks such as its renewal. You can also bring your own certificate (stored in Azure Key vault ) or even purchase a new certificate through Key vault and have Azure CDN use that certificate for securing the content delivery.&rdquo;</p></blockquote>
<h2 id="use-your-own-certificate">Use your own certificate</h2>
<p>There is also an option to use your own certificate, especially when you need a naked domain (without the &ldquo;www&rdquo;-prefix). This is not currently possible with the Build-In certificate option. See &ldquo;<a href="/hosting-a-static-site-on-azure-using-cdn-and-https/" rel="">Hosting a Static Site on Azure using CDN and HTTPS</a>&rdquo; how to fix this.</p>
]]></description></item><item><title>Upgrading my Amiga 1200 in the year 2020</title><link>https://clemens.ms/upgrading-my-amiga-1200-in-the-year-2020/</link><pubDate>Wed, 17 Jun 2020 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/upgrading-my-amiga-1200-in-the-year-2020/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><p>I learned all my basic computer and programming skills on Commodore computers, like the <strong>C64</strong>, <strong>Amiga 500</strong>, and <strong>1200</strong>. Twenty-eight years ago, I upgraded my <strong>Amiga 1200</strong> with a faster processor (<a href="https://en.wikipedia.org/wiki/Motorola_68030" target="_blank" rel="noopener noreffer ">Motorola 68030</a>) and extra memory (4 megabytes). I also added an FPU (Motorola 68882), a realtime clock, and an internal hard disk (120 megabytes), which is still working correctly in 2020 (wow). At that time, this was a fast Amiga.</p>
<p>After all those years, my Amiga needs some care. Specialty the capacitors on the mainboard need replacement, they are a danger for the PCB and the life of the Amiga. I also like to upgrade some hard- and software.</p>
<h2 id="workbench">Workbench</h2>
<p>The <strong>AmigaOS</strong> is called <strong>Workbench</strong>. By default, the Amiga 1200 comes with version 3.0. When upgrading the AmigaOS, you need also to update the Kickstart ROM (this is what the Amiga starts). In 2018 <a href="https://www.hyperion-entertainment.com/index.php/where-to-buy/direct-downloads/188-amigaos-314" target="_blank" rel="noopener noreffer ">Workbench version 3.1.4</a> came out, what adds new capabilities, like lager hard disks (4GB+), and fixes bugs from the past. So, I ordered this new version, including the Kickstart ROMs. In 2019 update 1 came out, so in the end, I now have <strong>Workbench 3.1.4.1</strong> running.</p>
<p>On top of the Workbench, I installed <a href="http://lilliput.amiga-projects.net/BetterWB.htm" target="_blank" rel="noopener noreffer ">BetterWB</a>. It is much like an enhancement, an updated extension to AmigaOS 3.1, without all those hardware requirement penalties typically associated with these kinds of packs.</p>
<h2 id="kickstart">Kickstart</h2>
<p>Upgrading the <a href="https://en.wikipedia.org/wiki/Kickstart_%28Amiga%29" target="_blank" rel="noopener noreffer ">Kickstart</a> ROMs requires to open the Amiga and replace the old ROMs with the new ones. Not very hard to do. I also removed the metallic shielding on top of the mainboard and did some cleaning.</p>
<h2 id="software">Software</h2>
<p>The most software I have is on disks. Every disk has only a capacity of 880KB and is getting older. So, I need a way to backup all my software. The Amiga 1200 (and the 600) has a PCMCIA port. I used this port to add a memory card adaptor. With the transfer tool, I copied all my disks to this memory card in an ADF (Amiga Disk File) format. I also made a backup of all the software on the old hard disk. Making a backup is just copying all the files; no special software needed.</p>
<h2 id="hardware">Hardware</h2>
<p>The 120 megabytes hard disk I replaced with an 8GB Compact Flash Drive. This new &lsquo;hard disk&rsquo; makes the Amiga much faster to boot up. I <a href="https://www.everythingamiga.com/2018/03/how-to-setup-an-amiga-compact-flash-drive-using-winuae.html" target="_blank" rel="noopener noreffer ">prepared the CF Drive</a> first on my Windows computer using <a href="http://www.winuae.net/" target="_blank" rel="noopener noreffer ">WinUAE</a> (the best Amiga Emulator), installed all the software, including the disks I backup earlier.</p>
<p>I also added a USB adaptor on the Game/Mouse port so that I can use a modern mouse.</p>
<h2 id="future-wishes">Future wishes</h2>
<p>The Amiga is outputting a 15KHz video signal, but modern monitors are using a 31KHz input signal. So I need a <a href="https://en.wikipedia.org/wiki/Flicker_fixer" target="_blank" rel="noopener noreffer ">flicker fixer</a> or scan doubler. I like to have the <a href="http://wiki.icomp.de/wiki/Indivision_AGA_MK2" target="_blank" rel="noopener noreffer ">Indivision AGA MK2</a>, but it is hard to find, so if you know a place that is selling the AGA MK2, please let me know.</p>
]]></description></item><item><title>My solar energy and Tesla Powerwall 2 setup</title><link>https://clemens.ms/my-solar-energy-and-tesla-powerwall-2-setup/</link><pubDate>Thu, 11 Jun 2020 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/my-solar-energy-and-tesla-powerwall-2-setup/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><p>Saving the planet a little bit while trying out new technology is fantastic, and in the end, it will save me also some money. When building my house last year, I invested in solar energy. The sun delivers me free electricity during the day, especially around noon, but I use it mainly at the end of the day and the beginning of the evening. Most of my solar energy goes to the public energy grid, and I need to repurchase it in the evening. This buying back makes no send to me. I like to use my own &lsquo;free&rsquo; solar energy, so I need a way to store it. After some research, I selected the <a href="https://www.tesla.com/powerwall" target="_blank" rel="noopener noreffer ">Tesla Powerwall 2</a> for my energy storage, not only for the best capacity, but my energy company (<a href="https://www.eneco.nl" target="_blank" rel="noopener noreffer ">Eneco</a>) had a good deal (2.500,- euro cashback). In the Netherlands, I can also get the tax back I paid for the solar and Tesla Powerwall 2 energy installation, an extra savings of 21%. The investment is still high, but after 5 or 6 years, it should be making money.</p>
<h2 id="hardware">Hardware</h2>
<p>My solar and energy installation exist out of different systems working closely together. From the solar panels, converter to the <strong>Tesla Gateway</strong> and <strong>Tesla Powerwall 2</strong>. The gateway needs the Internet for full functionality, it comes with a mobile connection, but that is more a fallback scenario. I placed the Tesla Powerwall 2 in the garage because it can make some low zooming noise if it is under load.</p>
<table>
  <thead>
      <tr>
          <th>System</th>
          <th>Brand</th>
          <th>Type</th>
          <th>Notes</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Solar panels</td>
          <td>Ja Solar</td>
          <td>JAM6K60-280-BK-35</td>
          <td>8 x 280Wp = <strong>2240 Wp</strong></td>
      </tr>
      <tr>
          <td>Converter</td>
          <td>Growatt</td>
          <td>2000-S</td>
          <td>DC to AC</td>
      </tr>
      <tr>
          <td>Gateway</td>
          <td>Tesla</td>
          <td>Gateway</td>
          <td>Connects to the Internet</td>
      </tr>
      <tr>
          <td>Battery</td>
          <td>Tesla</td>
          <td>Powerwall 2</td>
          <td><strong>13.5 kWh</strong></td>
      </tr>
  </tbody>
</table>
<h2 id="installation">Installation</h2>
<p>The Tesla Powerwall 2 arrived from the California factory in a big box and placed against the wall in my garage. There are two cables connected to the Powerwall, one for the communication between the Powerwall and the Gateway, and the second one for the power itself. It took the energy company around 6 hours to complete the setup with the Powerwall.</p>
<h2 id="software">Software</h2>
<p>To know what is going on with my energy installation, I use the <strong>Tesla App</strong> (it is the same as for the cars). In the Tesla App, I can see what my solar panels a producing, my housing is consuming and if the battery is charging. I can also use the web version when at home, but the information is limited, but it gives me a handy <a href="https://github.com/vloschiavo/powerwall2" target="_blank" rel="noopener noreffer ">Restful API</a>.</p>
<h2 id="performance">Performance</h2>
<p>The combination of the solar and the Powerwall gives me an excellent performance. In the table below, you can see the performance I had in the year 2020.</p>
<table>
  <thead>
      <tr>
          <th>Month</th>
          <th>Solar</th>
          <th>Powerwall</th>
          <th>Total</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>January 2020</td>
          <td>10%</td>
          <td>5%</td>
          <td>15%</td>
      </tr>
      <tr>
          <td>February 2020</td>
          <td>18%</td>
          <td>10%</td>
          <td>28%</td>
      </tr>
      <tr>
          <td>March 2020</td>
          <td>32%</td>
          <td>37%</td>
          <td>69%</td>
      </tr>
      <tr>
          <td>April 2020</td>
          <td>41%</td>
          <td>54%</td>
          <td>95%</td>
      </tr>
      <tr>
          <td>May 2020</td>
          <td>44%</td>
          <td>53%</td>
          <td>97%</td>
      </tr>
      <tr>
          <td>June 2020</td>
          <td>44%</td>
          <td>48%</td>
          <td>92%</td>
      </tr>
      <tr>
          <td>July 2020</td>
          <td>42%</td>
          <td>44%</td>
          <td>86%</td>
      </tr>
      <tr>
          <td>August 2020</td>
          <td>41%</td>
          <td>44%</td>
          <td>85%</td>
      </tr>
      <tr>
          <td>Setpember 2020</td>
          <td>33%</td>
          <td>35%</td>
          <td>68%</td>
      </tr>
      <tr>
          <td>October 2020</td>
          <td>17%</td>
          <td>6%</td>
          <td>23%</td>
      </tr>
      <tr>
          <td>November 2020</td>
          <td>14%</td>
          <td>8%</td>
          <td>22%</td>
      </tr>
      <tr>
          <td>December 2020</td>
          <td>7%</td>
          <td>2%</td>
          <td>9%</td>
      </tr>
  </tbody>
</table>
<p>and also by year:</p>
<table>
  <thead>
      <tr>
          <th>Year</th>
          <th>Solar</th>
          <th>Powerwall</th>
          <th>Total</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>2019</td>
          <td>31%</td>
          <td>31%</td>
          <td>62%</td>
      </tr>
      <tr>
          <td>2020</td>
          <td>31%</td>
          <td>26%</td>
          <td>57%</td>
      </tr>
      <tr>
          <td>2021</td>
          <td>29%</td>
          <td>24%</td>
          <td>53%</td>
      </tr>
      <tr>
          <td>2022</td>
          <td>15%</td>
          <td>18%</td>
          <td>33%</td>
      </tr>
      <tr>
          <td>2023</td>
          <td>16%</td>
          <td>43%</td>
          <td>59%</td>
      </tr>
  </tbody>
</table>
<h2 id="conclusion">Conclusion</h2>
<p>I&rsquo;m pleased with the setup and performance, but they&rsquo;re also some minor downsides I did not know before. In the Netherlands, the Tesla Powerwall 2 does not have all the functionality I had seen in other countries. Like by losing grid electricity, the Powerwall will not take over as a backup. Also, the Powerwall can&rsquo;t be used or buy cheap electricity at night from the grid.</p>
]]></description></item><item><title>Calculate the availability and SLA for your Azure solution</title><link>https://clemens.ms/calculate-the-availability-and-sla-for-your-azure-solution/</link><pubDate>Tue, 09 Jun 2020 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/calculate-the-availability-and-sla-for-your-azure-solution/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><p>Microsoft provides for most Azure services an <a href="https://azure.microsoft.com/en-us/support/legal/sla/" target="_blank" rel="noopener noreffer ">Service Level Agreements</a> (SLA), where you can find the availability for that services. The availability has a rage from <strong>99.9%</strong> to <strong>100%</strong> or no range at all (for some free services).</p>
<blockquote>
<p>&ldquo;We guarantee that 99.95% of the time, the Azure &hellip; Service will successfully receive and respond to &hellip;&rdquo;</p></blockquote>
<p>The SLA describes Microsoft&rsquo;s commitments for uptime and connectivity. It is also somewhat guaranteed, i.e., it is backed financially. It shows Microsoft will refund you when it fails the SLA, but it doesn&rsquo;t back your business.</p>
<h2 id="availability">Availability</h2>
<p>When building solutions on Azure, you use multiple Azure services, so how do you know the availability for your solution? Especially when the Azure services have different availability numbers.</p>
<p>First, we need to know which Azure Services are in the <strong>critical path</strong> for your solution, not all Azure Services you use are required for the availability. For example, Azure Application Insights is not vital for the uptime, but an Azure App- or Azure Storage service can be.</p>
<p>Next, we need to know what the availability is for every Azure Services in the critical path. We can look this up on the <a href="https://azure.microsoft.com/en-us/support/legal/sla/" target="_blank" rel="noopener noreffer ">SLA website</a> from Microsoft, but there is also a good overview on the <a href="https://azurecharts.com/sla" target="_blank" rel="noopener noreffer ">Azure SLA Board</a>.</p>
<p>Some services in your solution have multiple instances and working in parallel, or you have distributed the solution in multiple regions. We start with one instance/region to make it easier to calculate. Later we can also apply the calculation for multiple instances and regions.</p>
<h2 id="calculation-method">Calculation Method</h2>
<p>For the calculations of the availability, we use the <strong>probability theory</strong>. In probability theory, probabilities are expressed as a number between 0 and 1. We have two types of scenarios; services in serial, and service is parallel.</p>
<blockquote>
<p>&ldquo;<a href="https://en.wikipedia.org/wiki/Probability" target="_blank" rel="noopener noreffer ">Probability</a> is the branch of mathematics concerning numerical descriptions of how likely an event is to occur or how likely it is that a proposition is true.&rdquo;</p></blockquote>
<h2 id="services-in-serial">Services in Serial</h2>
<p>For calculating the combined availability of services in <strong>serial</strong>, we make use of the <em>Multiplication Theorem for Independent Events</em>. The equation of the multiplication of independent events is shown below (The symbol $\cap$ means ‘and’).</p>
<p>This equation is based on the <em>Multiplication Theorem on Probability</em>, $P\left(A\cap B\right)\ =P\left(A\right)\cdot P\left(B\middle| A\right)$ and the definition of independent events, $P(B|A)\ =\ P(B)$:</p>
<blockquote>
<p>$P(A\ \cap\ B)\ =\ P(A)\ \cdot\ P(B)$</p></blockquote>
<p>In our situation, we want to know whether our Azure services for our solution is available. Our solution has two Azure services, an Web App- and SQL Database service.</p>
<p>The <strong>Web App</strong> has a availability of <a href="https://azure.microsoft.com/en-us/support/legal/sla/app-service/" target="_blank" rel="noopener noreffer "><strong>99.95%</strong></a> and <strong>SQL Database</strong> of <a href="https://azure.microsoft.com/en-us/support/legal/sla/sql-database/" target="_blank" rel="noopener noreffer "><strong>99.99%</strong></a>, then our equation results in:</p>
<blockquote>
<p>$P(&ldquo;Web\ App\ is\ up\ \cap\ SQL\ DB\ is\ up&rdquo;)\\\ =\ P(&ldquo;Web\ App\ is\ up&rdquo;\ )\ \cdot P(&ldquo;SQL\ DB\ is\ up&rdquo;)\\\ =\ 99.95\ \cdot\ 99.99\\\ =\ 99.94$</p></blockquote>
<p>This results in that our whole infrastructure has an availability of <strong>99.94%</strong>. Each component can fail independently, so the more components, the more failures can occur.</p>
<h2 id="services-in-parallel">Services in Parallel</h2>
<p>When services are in <strong>parallel</strong>, the situation changes. The availability doesn’t depend on the services together. For the solution were services $A$ and $B$ in parallel to be available, $A\ \cup\ B$ (The symbol $\cup$ means ‘or’) needs to be available. When one of them is down, the solution is still available.</p>
<p>To calculate the availability of this solution, we need to know the probability that a service is not available. The probability that service $A$ is not available is defined as:</p>
<blockquote>
<p>$P\left(\lnot A\right)=1-P\left(A\right)$</p></blockquote>
<p>In the case of parallel services, the system is available, when as long as both services $A$ and $B$ are not down. We can calculate that as:</p>
<blockquote>
<p>$P\left(A\cup B\right)=1-P\left(\lnot A\cap\lnot B\right)\\\ =1-\left(P\left(\lnot A\right)\cdot P\left(\lnot B\right)\right)\\\ =1-\left(\left(1-P\left(A\right)\right)\cdot\left(1-P\left(B\right)\right)\right)$</p></blockquote>
<p>Here, we used the definition when a service is not available and the <em>multiplication theorem</em> from above. To calculate the availability of more than two services in parallel, we can calculate the availability of $A$ and $B$ first and then see $A$ and $B$ as a ‘single service’ and use the same equation again to calculate together with $C$.</p>
<blockquote>
<p>$P\left(A\cup B\cup C\right)=1-\left(\left(1-P\left(A\right)\right)\cdot\left(1-P\left(B\right)\right)\cdot\left(1-P\left(C\right)\right)\right)\\P\left(A\cup B\cup C\right)=1-\left(\left(1-P\left(A\ \cap\ B\right)\right)\cdot\left(1-P\left(C\right)\right)\right)$</p></blockquote>
<blockquote>
<p>$P(Primary\ or\ Secondary\ is\ up)\ =\ 1\ -\ (1\ -\ P(Primary\ is\ up))\ \cdot\ (1\ -\ P(Secondary\ is\ up))\\\ =\ 1\ -\ (1\ -\ 99.94)\ \cdot\ (1\ -\ 99.94)\\\ =\ 1\ -\ 0.06 \cdot\ 0.06\\\ =\ 1\ -\ 0.000036\\\ =\ 99.999964$</p></blockquote>
<p>We can boost the availability of a solution by implementing a multi-region failover. This increases the complexity and cost of the solution.</p>
<h2 id="theoretical-availability">Theoretical availability</h2>
<p>The calculations above are a theoretical availability of the solution, based on the SLA&rsquo;s from Microsoft. The actual availability should be much higher, have a look at the <a href="https://status.azure.com/en-us/status/history/" target="_blank" rel="noopener noreffer ">Azure status history &amp; Root Cause Analysis (RCAs)</a> site. It is essential to <strong>measure the solution</strong> to get a realistic availability number and SLA.</p>
]]></description></item><item><title>Using Visual Studio Code with PHP and Xdebug</title><link>https://clemens.ms/using-visual-studio-code-with-php-and-xdebug/</link><pubDate>Mon, 08 Jun 2020 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/using-visual-studio-code-with-php-and-xdebug/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><p>This post describes how to set up, run, and debug <strong>PHP</strong> code with <strong>Visual Studio Code</strong> running on Windows.</p>
<h2 id="what-do-we-need">What do we need?</h2>
<p>Compared to some other popular programming languages for the Web, PHP is missing some tools, so I created a shopping list to make the experience more complete.</p>
<p>Basically what we need is:</p>
<ul>
<li>the PHP runtime itself,</li>
<li>a modern code editor with syntax highlighting,</li>
<li>a debugger connected to our code editor,</li>
<li>and a (development) webserver.</li>
</ul>
<h2 id="runtime-php">Runtime (PHP)</h2>
<p>We start with the <strong>PHP</strong> runtime. The PHP runtime will pars en execute our <code>.php</code> files and outputs the HTML (or anything you want) back to you.</p>
<ol>
<li>Download <a href="https://windows.php.net/download#php-7.4" target="_blank" rel="noopener noreffer ">PHP version 7.4</a> (VC15 x64 Non Thread Safe) and extract the archive to <code>c:\php\</code>.</li>
<li>Add the folder <code>c:\php\</code> to your path, so we can easily access it. <code>setx path &quot;%path%;C:\php\&quot;</code></li>
</ol>
<blockquote>
<p>Recent versions of PHP are built with Visual Studio 2019. You need to install the <a href="https://visualstudio.microsoft.com/downloads/#microsoft-visual-c-redistributable-for-visual-studio-2019" target="_blank" rel="noopener noreffer ">Visual C++ Redistributable for Visual Studio 2019</a> before PHP can run.</p>
<p><code>winget install &quot;Microsoft Visual C++ 2015-2019 Redistributable (x64)&quot;</code></p></blockquote>
<ol start="3">
<li>Now we can run PHP <code>php -v</code> and see if it works. It should show you the version number and copyright information.</li>
</ol>
<h2 id="debugger-xdebug">Debugger (Xdebug)</h2>
<p>To debug PHP code, we need to add an extension <strong>Xdebug</strong> to PHP.</p>
<ol>
<li>Download <a href="https://xdebug.org/download" target="_blank" rel="noopener noreffer ">Xdebug 2.9</a> for PHP 7.4 (VC15 64 bit) and place the DLL into the folder <code>C:\php\ext</code>.</li>
<li>In the folder <code>c:\php\</code> <strong>rename</strong> the file <code>php.ini-development</code> to <code>php.ini</code>.</li>
<li>Add to the following lines to the file <code>php.ini</code> to tell PHP to use Xdebug:</li>
</ol>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><pre tabindex="0"><code>zend_extension=C:\php\ext\php_xdebug-2.9.6-7.4-vc15-nts-x86_64.dll
xdebug.remote_enable = 1
xdebug.remote_autostart = 1</code></pre></div>
<ol start="4">
<li>Check if Xdebug is part of PHP, use the <code>php -v</code> command again, and see if you get the response <strong>with Xdebug v2.9.6</strong>.</li>
</ol>
<h2 id="webserver-iis-express">Webserver (IIS Express)</h2>
<p>When creating web applications/sites, we also need a webserver. PHP does not have a development web server, so we need to use something else to serve our pages. Under Windows, it is common to use Microsoft <strong>Internet Information Services</strong> (IIS). We are using the lightweight (nonproduction) version IIS Express.</p>
<ol>
<li>Download <a href="https://www.microsoft.com/en-us/download/details.aspx?id=48264" target="_blank" rel="noopener noreffer ">IIS 10 Express</a>.</li>
<li>Start the setup and install it on your system</li>
<li>Run IIS for the first time and quit (we need the initial configuration files)</li>
</ol>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-cmd">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl"><span class="k">cd</span> c:\Program Files\IIS Express\
</span></span><span class="line"><span class="cl">iisexpress
</span></span><span class="line"><span class="cl">Starting IIS Express ...
</span></span><span class="line"><span class="cl">Successfully registered URL <span class="s2">&#34;http://localhost:8080/&#34;</span> for site <span class="s2">&#34;WebSite1&#34;</span> application <span class="s2">&#34;/&#34;</span>
</span></span><span class="line"><span class="cl">Registration completed for site <span class="s2">&#34;WebSite1&#34;</span>
</span></span><span class="line"><span class="cl">IIS Express is running.
</span></span><span class="line"><span class="cl">Enter &#39;Q&#39; to stop IIS Express</span></span></code></pre></div></div>
<ol start="4">
<li>We need to tell IIS to use the PHP runtime (using fastCGI) and associate <code>.php</code> files with it. We also tell IIS to use <code>index.php</code> as a starting point:</li>
</ol>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-cmd">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">appcmd set config /section:system.webServer/fastCGI /+[fullPath=&#39;<span class="s2">&#34;C:\php\php-cgi.exe&#34;</span>&#39;]
</span></span><span class="line"><span class="cl">appcmd set config /section:system.webServer/handlers /+[name=&#39;PHP_via_FastCGI&#39;,path=&#39;*.php&#39;,verb=&#39;*&#39;,modules=&#39;FastCgiModule&#39;,scriptProcessor=&#39;<span class="s2">&#34;C:\php\php-cgi.exe&#34;</span>&#39;,resourceType=&#39;Unspecified&#39;]
</span></span><span class="line"><span class="cl">appcmd set config /section:system.webServer/defaultDocument /+<span class="s2">&#34;files.[value=&#39;index.php&#39;]&#34;</span></span></span></code></pre></div></div>
<h2 id="code-editor-vscode">Code editor (VSCode)</h2>
<p><strong>Visual Studio Code</strong> is a cross-platform, free, and opensource code editor. I use it for most of my projects.</p>
<ol>
<li>
<p>Download <a href="https://code.visualstudio.com/" target="_blank" rel="noopener noreffer ">Visual Studio Code</a> or use WinGet <code>winget install &quot;Visual Studio Code&quot;</code></p>
</li>
<li>
<p>Run the setup and install it on your system</p>
</li>
<li>
<p>Add the two following extensions to Visual Studio Code:</p>
<ul>
<li><a href="https://marketplace.visualstudio.com/items?itemName=warren-buckley.iis-express" target="_blank" rel="noopener noreffer ">IIS Express</a></li>
<li><a href="https://marketplace.visualstudio.com/items?itemName=felixfbecker.php-debug" target="_blank" rel="noopener noreffer ">PHP Debug</a></li>
</ul>
</li>
<li>
<p>In your Visual Studio Code <strong>project</strong>, go to the debugger and hit the little <strong>gear icon</strong> ⚙️ and choose <strong>PHP</strong>. A new launch configuration file is created for you <code>launch.json</code>. This will start listening on the specified port (by default 9000) for Xdebug. Every time you make a request with a browser to your web server, Xdebug will connect, and you can stop on breakpoints, exceptions, etc.</p>
</li>
</ol>
<p>More information how to use the debugger in Visual Studio Code, you can read here <a href="https://code.visualstudio.com/Docs/editor/debugging" target="_blank" rel="noopener noreffer ">https://code.visualstudio.com/Docs/editor/debugging</a></p>
<h2 id="visual-studio-code-user-settings">Visual Studio Code User Settings</h2>
<p>To configure Visual Studio Code to use PHP, add the following line to the user configuration.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-json">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><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;php.validate.executablePath&#34;</span><span class="p">:</span> <span class="s2">&#34;c:/php/php.exe&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p>You now ready to develop, run, and debug PHP code on Windows.</p>
]]></description></item><item><title>CaveRace</title><link>https://clemens.ms/caverace/</link><pubDate>Thu, 04 Jun 2020 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/caverace/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><p><strong>CaveRace</strong> is a classic maze-based video game I developed back in <strong>1997</strong> with some other students. Inspired on a game, I played on my Commodore Amiga, called <strong>Dyna Blaster</strong> <a href="https://en.wikipedia.org/wiki/Bomberman_%281990_video_game%29" target="_blank" rel="noopener noreffer ">(Bomberman)</a>.</p>
<table>
  <thead>
      <tr>
          <th>Forest level</th>
          <th>Winter level</th>
          <th>Lava level</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td></td>
          <td></td>
          <td></td>
      </tr>
  </tbody>
</table>
<h2 id="download">Download</h2>
<p>You can <strong><a href="/caverace/caverace-1.2-dos.zip" rel="">download</a></strong> the 16-bit MS-DOS version of CaveRace (version 1.2) here.</p>
<h2 id="story">Story</h2>
<p>On the planed Eldora, there are miners collecting gold and diamonds. When the miners get unexpected visitors from outer space, the mining of precious minerals is in danger. The only defense the miners have is their explosions to make a way inside the mines and caves. CaveRace is a game where you collect as much as possible precious minerals and destroy all alien visitors.</p>
<table>
  <thead>
      <tr>
          <th>Planed Eldora</th>
          <th>Collect treasure</th>
          <th>Alien visitors</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td></td>
          <td></td>
          <td></td>
      </tr>
  </tbody>
</table>
<h2 id="borland-c">Borland C</h2>
<p>The game is mainly written in the programming language <a href="https://en.wikipedia.org/wiki/Borland_C%2B%2B" target="_blank" rel="noopener noreffer ">Borland C</a> 3.1, and some graphic routines are in x68 assembly language. The minimal system requirements are an Intel 80386 IBM compatible PC running MS-DOS and VGA 320×200 pixels (Mode 13h) in 256-color mode video system.</p>
<p>The game is using your mouse and keyboard for navigation and gameplay. The game loop is in sync with the refresh rate of the screen, not the best choice, but simple to understand. No sound is present in the MS-DOS version, later on, I ported CaveRace to Windows, this version has upgraded graphics and sounds (explosions).</p>
<p>There is also a <strong>MapEditor</strong> available to create levels yourselves. Let me know what you have created.</p>
<h2 id="graphics">Graphics</h2>
<p>The original artwork is created on an <strong>Amiga</strong> using <a href="https://en.wikipedia.org/wiki/Deluxe_Paint" target="_blank" rel="noopener noreffer ">Deluxe Paint</a> in the file format IFF (Interchange File Format). The game CaveRace is using a binary RGB byte file format. Game tiles (16 x 16 pixels) and screens (320 x 200 pixels) are converted to this binary RGB byte file format.</p>
<table>
  <thead>
      <tr>
          <th>File</th>
          <th>Tiles</th>
          <th>Bytes</th>
          <th>Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>BGS</td>
          <td>5 x 50(16x16)</td>
          <td>64000</td>
          <td>BackGrounds</td>
      </tr>
      <tr>
          <td>BOM</td>
          <td>17 x (16x16)</td>
          <td>4352</td>
          <td>Boms</td>
      </tr>
      <tr>
          <td>CAR</td>
          <td>320 x 200</td>
          <td>64000</td>
          <td>Carder (Picture)</td>
      </tr>
      <tr>
          <td>ENM</td>
          <td>16 x (16x16)</td>
          <td>4096</td>
          <td>Enemys</td>
      </tr>
      <tr>
          <td>FNT</td>
          <td>36 x (3x5)</td>
          <td>540</td>
          <td>Font</td>
      </tr>
      <tr>
          <td>HIS</td>
          <td>320 x 200</td>
          <td>64000</td>
          <td>Hi-Scores (Picture)</td>
      </tr>
      <tr>
          <td>ITM</td>
          <td>13 x (16x16)</td>
          <td>3328</td>
          <td>Items</td>
      </tr>
      <tr>
          <td>MAN</td>
          <td>18 x (16x16)</td>
          <td>4608</td>
          <td>Man</td>
      </tr>
      <tr>
          <td>MN1</td>
          <td>320 x 200</td>
          <td>64000</td>
          <td>Menu 1 (Picture)</td>
      </tr>
      <tr>
          <td>MN2</td>
          <td>320 x 200</td>
          <td>64000</td>
          <td>Menu 2 (Picture)</td>
      </tr>
      <tr>
          <td>PAL</td>
          <td>256 x (RGB)</td>
          <td>768</td>
          <td>Palette</td>
      </tr>
      <tr>
          <td>STS</td>
          <td>4 x (16x16)</td>
          <td>1024</td>
          <td>Status</td>
      </tr>
      <tr>
          <td>TRS</td>
          <td>6 x (16x16)</td>
          <td>1536</td>
          <td>Treacure</td>
      </tr>
  </tbody>
</table>
<h2 id="game-cheats">Game cheats</h2>
<p>Start the game using the switch <strong>-powerblast</strong>, and now you can use the function keys for additional power.</p>
<table>
  <thead>
      <tr>
          <th>Key</th>
          <th>Result</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>F1</td>
          <td>next level</td>
      </tr>
      <tr>
          <td>F2</td>
          <td>max. health</td>
      </tr>
      <tr>
          <td>F3</td>
          <td>max. bombs</td>
      </tr>
      <tr>
          <td>F4</td>
          <td>more bomb power</td>
      </tr>
      <tr>
          <td>F5</td>
          <td>double points</td>
      </tr>
      <tr>
          <td>1</td>
          <td>screen capture, output is saved to the file screen.raw</td>
      </tr>
      <tr>
          <td>%</td>
          <td>shows the rendering time</td>
      </tr>
  </tbody>
</table>
<p>When running the game on old and slow systems, you can use the <strong>-slow</strong> switch to speed up the game.</p>
<h2 id="open-source">Open Source</h2>
<p>The MS-DOS version of <a href="https://github.com/cschotte/caverace" target="_blank" rel="noopener noreffer ">CaveRace is opensource</a> under the Apache-2.0 license and can found op GitHub.</p>
<h2 id="caverace-for-windows">CaveRace for Windows</h2>
<p>There is also a Microsoft <strong>Windows</strong> and <strong>Windows Phone 7</strong> version of CaveRace. Written in <strong>C#</strong> and using <strong>DirectX</strong> graphics. More information about how I ported the C version to C# in an upcoming blog post.</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/4rbRBwpPcKs?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<table>
  <thead>
      <tr>
          <th>Desert &amp; Lava levels</th>
          <th>Forest &amp; Winter level</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td></td>
          <td></td>
      </tr>
      <tr>
          <td></td>
          <td></td>
      </tr>
  </tbody>
</table>
]]></description></item><item><title>Generate PDF files with asp.net core on Azure</title><link>https://clemens.ms/generate-pdf-files-with-asp.net-core-on-azure/</link><pubDate>Thu, 04 Jun 2020 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/generate-pdf-files-with-asp.net-core-on-azure/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><p>There are many libraries and services to generate PDF files for asp.net core web applications. There are excellent commercial solutions out there, but if you need a free solution, it gets harder. Some libraries are hard to use, or others are limited in functionality. I need a free, easy to use, and quick solution to generate PDF files on an <a href="https://azure.microsoft.com/en-us/services/app-service/web/" target="_blank" rel="noopener noreffer ">Azure Web App</a>.</p>
<h2 id="can-a-view-retrun-a-pdf">Can a View retrun a PDF?</h2>
<p>What I need is a View that returns a PDF and not HTML what it usually does. The beauty of using a standard View is that I can use my web and asp.net core knowledge to design the View. In this case, I need to generate invoices.</p>
<blockquote>
<p>I&rsquo;m using MVC as a pattern to structure my web project.</p></blockquote>
<p>I start by creating an invoice controller and a correlated view, where I can design an invoice in HTML.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-csharp">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">InvoiceController</span> <span class="p">:</span> <span class="n">Controller</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">IActionResult</span> <span class="n">Index</span><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="k">return</span> <span class="n">View</span><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></code></pre></div></div>
<h2 id="html-to-pdf">html to pdf</h2>
<p>Next, we need a way to render the HTML (the View) as PDF. I use the opensource command-line tool <a href="https://wkhtmltopdf.org/" target="_blank" rel="noopener noreffer ">wkhtmltopdf</a>. It is easy to use and compatible to run on Azure Web Apps.</p>
<blockquote>
<p>wkhtmltopdf is an opensource (LGPLv3) command-line tool to render HTML into PDF using the Qt WebKit rendering engine. It runs entirely &ldquo;headless.&rdquo;</p></blockquote>
<p><a href="https://wkhtmltopdf.org/downloads.html" target="_blank" rel="noopener noreffer ">Download</a> wkhtmltopdf and add the files to your project. I created a folder <code>/wkhtmltopdf</code> in my solution. When downloading, pick the correct version that is compatible with your Azure Web App (Windows or Linux).</p>
<p>Add the files <code>wkhtmltoimage.exe</code>, <code>wkhtmltopdf.exe</code>, and <code>wkhtmltox.dll</code> to your solution and change the &lsquo;Copy to Output Directory&rsquo; to &lsquo;Copy always.&rsquo;</p>
<h2 id="rendering-the-pdf">Rendering the PDF</h2>
<p>An easy way to interact with the wkhtmltopdf command-line tool is to use the <a href="https://github.com/webgio/Rotativa.AspNetCore" target="_blank" rel="noopener noreffer ">Rotativa</a> library from NuGet.</p>
<p>First, we need to tell Rotativa where to find the wkhtmltopdf tool. Add the following line to <code>Startup.cs</code></p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-csharp">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">void</span> <span class="n">Configure</span><span class="p">(</span><span class="n">IApplicationBuilder</span> <span class="n">app</span><span class="p">,</span> <span class="n">IWebHostEnvironment</span> <span class="n">env</span><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></span><span class="line"><span class="cl">    <span class="n">RotativaConfiguration</span><span class="p">.</span><span class="n">Setup</span><span class="p">(</span><span class="n">env</span><span class="p">.</span><span class="n">ContentRootPath</span><span class="p">,</span> <span class="s">&#34;wkhtmltopdf&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p>Next, we create a new action result in our InvoiceController that will return the PDF file. This new action result &lsquo;IndexAsPDF&rsquo; will reuse the View we already had created.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-csharp">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">InvoiceController</span> <span class="p">:</span> <span class="n">Controller</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></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">IActionResult</span> <span class="n">Pdf</span><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="k">return</span> <span class="k">new</span> <span class="n">ViewAsPdf</span><span class="p">(</span><span class="s">&#34;Index&#34;</span><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="n">FileName</span> <span class="p">=</span> <span class="s">&#34;Invoice.pdf&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">PageSize</span> <span class="p">=</span> <span class="n">Size</span><span class="p">.</span><span class="n">A4</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">MinimumFontSize</span> <span class="p">=</span> <span class="m">16</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">ContentDisposition</span> <span class="p">=</span> <span class="n">ContentDisposition</span><span class="p">.</span><span class="n">Inline</span><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></span></code></pre></div></div>
<p>When we go to the URL <code>https://localhost:44349/Invoice/Index</code> it will show the HTML version of the Invoice, but when we go to <code>https://localhost:44349/Invoice/Pdf</code> we get the PDF version.</p>
]]></description></item><item><title>Protect the UniFi Cloud Key with a custom SSL certificate</title><link>https://clemens.ms/protect-the-unifi-cloud-key-with-a-custom-ssl-certificate/</link><pubDate>Wed, 03 Jun 2020 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/protect-the-unifi-cloud-key-with-a-custom-ssl-certificate/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.png" referrerpolicy="no-referrer">
            </div><p>When connecting to the <strong>UniFi Cloud Key Controller</strong>, you need to know the IP-address and port number (default 8443). In my situation, the IP-address is 192.168.1.64</p>
<blockquote>
<p>If you do not know your Cloud Key IP-address, use the <a href="https://www.ui.com/download/unifi/" target="_blank" rel="noopener noreffer ">Ubiquiti Device Discovery Tool</a>.</p></blockquote>
<p>The connection is using <strong>https</strong> (that&rsquo;s good), but there is no domain name, so the default SSL certificate triggers a security warning in your web browser because it can&rsquo;t be verified. Also, the web browser will not remember your login credentials for the next login.</p>
<h2 id="custom-hostname">Custom hostname</h2>
<p>In the Cloud Key Controller, we can use a custom hostname (under <em>Controller settings -&gt; Advanced Configuration</em>). I created a sub-domain &lsquo;unifi&rsquo; at my DNS provider (I use <a href="https://azure.microsoft.com/en-us/services/dns/" target="_blank" rel="noopener noreffer ">Azure DNS</a> for all my domains) and pointed an A-record to the UniFi Cloud Key IP address. This new hostname gives me a friendly URL, but the SSL warning is still there.</p>
<h2 id="adding-a-ssl-certificate">Adding a SSL certificate</h2>
<p>We can now access the Cloud Key with a custom hostname, but we also need to add a custom SSL certificate to the controller. The SSL certificate needs to have the same domain name associated as in the custom hostname field. I used the same wildcard SSL certificate I hold for my public domain name.</p>
<p>My SSL certificate files are in a different format for what we need. We need to create a P12 bunded certificate archive and transfer this to the Cloud Key. To create a P12 bunded certificate archive, I use <a href="https://www.openssl.org/" target="_blank" rel="noopener noreffer ">OpenSSL</a>.</p>
<blockquote>
<p>Under Windows 10, I use the <a href="https://docs.microsoft.com/en-us/windows/wsl/install-win10" target="_blank" rel="noopener noreffer ">Windows Subsystem for Linux</a> (WSL) and Ubuntu. No need to install OpenSSL for Windows.</p></blockquote>
<p>This will create a P12 bunded certificate archive with a temporary password we use later.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-bash">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">openssl pkcs12 -export -inkey certificate.key -in certificate.crt -out certificate.p12 -name ubnt -password pass:temppass</span></span></code></pre></div></div>
<p>Copy the P12 certificate from your local computer to the Unifi Cloud Key. I put it in the &lsquo;/home&rsquo; folder.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-bash">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">scp certificate.p12 ubnt@192.168.1.64:/home</span></span></code></pre></div></div>
<h2 id="enable-the-new-ssl-certificate">Enable the new SSL certificate</h2>
<p>First, we need to login into the Cloud Key and stop the running services. The <strong>ubnt</strong> is the default user.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-bash">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ssh 192.168.1.64 -l ubnt</span></span></code></pre></div></div>
<blockquote>
<p>I like to use <a href="https://midnight-commander.org/" target="_blank" rel="noopener noreffer ">Midnight Commander</a> (MC)  under Linux, to install MC use the following <strong>optional</strong> commands:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-bash">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo apt-get update
</span></span><span class="line"><span class="cl">sudo apt-get install mc</span></span></code></pre></div></div></blockquote>
<p>Stop the nginx and unifi services on the cloud key</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-bash">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">service nginx stop
</span></span><span class="line"><span class="cl">service unifi stop</span></span></code></pre></div></div>
<p>The next step is to remove the symbolic link and the reference to the built-in self-signed SSL certificate inside the Cloud Key.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-bash">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">rm /usr/lib/unifi/data/keystore</span></span></code></pre></div></div>
<p>And remove the following line &ldquo;<strong>UNIFI_SSL_KEYSTORE=/etc/ssl/private/unifi.keystore.jks</strong>&rdquo; from the file &ldquo;/etc/default/unifi&rdquo;. It shoud be the last line in the file.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-bash">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mcedit /etc/default/unifi</span></span></code></pre></div></div>
<p>Now we can install the new SSL certificate. We use the temporary password we created earlier.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-bash">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo keytool -importkeystore -deststorepass aircontrolenterprise -destkeypass aircontrolenterprise -destkeystore /usr/lib/unifi/data/keystore -srckeystore /home/certificate.p12 -srcstoretype PKCS12 -srcstorepass temppass -alias ubnt -noprompt</span></span></code></pre></div></div>
<p>This will give a warning you can ignore: &ldquo;Warning: The JKS keystore uses a proprietary format &hellip;&rdquo;.</p>
<p>Restart the nginx and unifi services we stopped.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-bash">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">service nginx start
</span></span><span class="line"><span class="cl">service unifi start</span></span></code></pre></div></div>
<h2 id="test-en-clean-up">Test en clean up</h2>
<p>Let&rsquo;s test if we can now access the Cloud Key using the new hostname and custom SSL certificate.</p>
<p>If everything is working, we need to clean up the temporary P12 certificate.</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-bash">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">rm /home/certificate.p12</span></span></code></pre></div></div>
]]></description></item><item><title>Hosting a Static Site on Azure using CDN and HTTPS</title><link>https://clemens.ms/hosting-a-static-site-on-azure-using-cdn-and-https/</link><pubDate>Thu, 28 May 2020 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/hosting-a-static-site-on-azure-using-cdn-and-https/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.png" referrerpolicy="no-referrer">
            </div><p>Most websites don’t need a dynamically generated page for every visitor; it is slow, expensive, and requires continuous updates to be secure. A static site is fast and reliable. I hear you thinking; this is old school, most websites are interactive and are using a CMS in some kind to manage their content.</p>
<h2 id="headless-cms">Headless CMS</h2>
<p>The solution for a static website is to make use of a headless CMS, like <a href="https://gohugo.io/" target="_blank" rel="noopener noreffer ">Hugo</a> or <a href="https://jekyllrb.com/" target="_blank" rel="noopener noreffer ">Jekyll</a>. Basascly, you generate a static site from content and a template, similar to what this blog is doing.</p>
<h2 id="a-fast-and-scalable-website">A fast and scalable website</h2>
<p><a href="https://azure.microsoft.com/" target="_blank" rel="noopener noreffer ">Microsoft Azure</a> Cloud platform is the ideal candidate to host your website and web applications. Usually, when I create a website, I use <strong>App Service Web Apps</strong>, which runs your solution in a scalable manner.</p>
<p>But there is also another option, which is cheaper and is very fast. You can also host your static website from a <a href="https://azure.microsoft.com/en-us/services/storage/blobs/" target="_blank" rel="noopener noreffer ">Storage Account</a>, and when you combine this with Azure <a href="https://azure.microsoft.com/en-us/services/cdn/" target="_blank" rel="noopener noreffer ">Content Delivery Network</a> (CDN), your site is lightning fast and available worldwide.</p>
<h2 id="storage-account">Storage Account</h2>
<p>We start by creating a general-purpose storage account in Azure. Use a unique name and choose a location close to yourself. I live in the Netherlands, so West Europe is, in my case, the best option. Replication I don’t need, so locally redundant, is the cheapest for me.</p>
<p>Next, we need to <strong>enable the Static website option</strong> in the storage account, and which also gives us the primary endpoint URL we need later.</p>
<p>Upload some test HTML content into the BLOB storage. I used the build-in Storage Explorer when uploading. Use the container <strong>$web</strong> (this was created by enabling the Static website option).</p>
<p>Let’s test if the files are visible in the browser. Use the primary endpoint URL from the enable the Static website option. It looks something like: <code>https://«your unique name».z6.web.core.windows.net/</code></p>
<blockquote>
<p>The z6 in the URL is the location of the datacenter and can be different in your situation.</p></blockquote>
<h2 id="content-delivery-network-cdn">Content Delivery Network (CDN)</h2>
<p>To make your website fast and responsive, host it closeby your visitors. Visitors from the US and Australia are far from West Europe, so by using a CDN, the content is as closeby as possible. Azure CDN has <a href="https://docs.microsoft.com/en-us/azure/cdn/cdn-pop-locations" target="_blank" rel="noopener noreffer ">130 point-of-presence (POP) locations</a> worldwide.</p>
<p>Creating an Azure CDN is easy, give it a unique name and point it to the primary endpoint URL from the Storage account we had created earlier.</p>
<p>Test if your site is still working, and go to the CDN edge URL. It looks something like: <code>https://«your unique name».azureedge.net/</code></p>
<h2 id="domain-names-and-azure-dns">Domain names and Azure DNS</h2>
<p>Our site is now working, served by a storage account, and cached by a CDN worldwide. Now we need a beautiful domain name and URL before we can communicate this to our visitors.</p>
<p>I require a subdomain “www” and a naked/root domain without the “www” prefix pointing to the CDN edge URL. For the subdomain, this is easy, use a CNAME-record. But for a naked/root domain, it is a little bit more complicated. You can’t use a CNAME, but need an A- and AAAA-record pointing to an IP address. But how do I know the right IP address for the Azure CDN edge, and what happens if this IP address changes (and changes happen)? Luckily, when using Azure DNS, we can point to Azure resources what automatically takes the right IP address. We add the naked/root domain and subdomain to the CDN under Custom domains. And test if the website is working in our browsers.</p>
<h2 id="traffic-encryption-using-ssl-for-https">Traffic encryption using SSL for HTTPS</h2>
<p>If your website is not using HTTPS, most browsers warn you nowadays. To make the traffic secure between your visitors and the site, we need encryption. We do this by enabling HTTPS at the CDN endpoint. By enabling HTTPS, you get a free certificate that automatically renews. There is a catch if you need HTTPS for a naked/root domain; you need to bring your certificate. Store your certificate in a Key Vault and give the CDN permissions to read this certificate. Then it is possible to enable HTTPS for a naked/root domain.</p>
<h2 id="url-rewire-rules">URL rewire rules</h2>
<p>I don’t like the “www” prefix before my domain, but people still type this. To make it easy for people, I want to redirect traffic to the right URL.</p>
<ol>
<li>Trafic to “www” needs to go to my naked/root domain.</li>
<li>Trafic that is not using HTTPS (encryption) needs to go to HTTPS.</li>
</ol>
<p>The Azure CDN Rules engine is more than capable of rewriting the URL, and I used HTTP 308 (permanent redirect) to redirect the traffic to the right place.</p>
<h2 id="azure-static-web-apps">Azure Static Web Apps</h2>
<blockquote>
<p>Azure <a href="https://azure.microsoft.com/en-us/services/app-service/static/" target="_blank" rel="noopener noreffer ">Static Web Apps</a> (currently in preview) gives you similar capabilities combined with a GitHub native workflow and serverless API’s.</p></blockquote>
]]></description></item><item><title>KPN Fiber Connection with Ubiquiti USG, IPTV and IPv6</title><link>https://clemens.ms/kpn-fiber-connection-with-ubiquiti-usg-iptv-and-ipv6/</link><pubDate>Mon, 25 May 2020 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/kpn-fiber-connection-with-ubiquiti-usg-iptv-and-ipv6/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><p>In my last blog post, I talked about <a href="/my-home-and-office-network-setup/" rel="">my Home and Office Network Setup</a> and explained that the default modem/router/Wi-Fi device you get from your ISP is not the best thing to have in your network. In this blog post, I explain what I did to directly connect my KPN fiber connection to my Ubiquiti Security Gateway (USG).</p>
<blockquote>
<p><strong>Ziggo</strong> When using a cable connection, a modem is needed to modulate the signal to be useful as TCP/IP. In this case, the only option you have is to set up the modem in <a href="https://www.ziggo.nl/klantenservice/wifi/modem/bridge-modus" target="_blank" rel="noopener noreffer ">Bridge mode</a>. When your modem is in Bridge mode, it will give you a public IPv4 address, and all other functions like Wi-Fi and firewall are disabled. <em>Note, IPv6 is currently not supported in Bridge mode.</em></p></blockquote>
<p>When using a fiber connection, you do not need a modem. The fiber connection is already TCP/IP, only the medium (fiber) needs conversion to Ethernet. KPN comes with a medium converter (FTTH), which gives you a TCP port with a public IP (VLAN 6) and IPTV (VLAN 4).</p>
<h2 id="ubiquiti-usg">Ubiquiti USG</h2>
<p>Not all settings are available in the UniFi controller web interface. So we need some configuration scripts. I used the <a href="https://github.com/coolhva/usg-kpn-ftth" target="_blank" rel="noopener noreffer ">scripts from Henk van Achterberg</a>. He created excellent scripts that work for my situation.</p>
<ol>
<li>
<p>Download the <a href="https://github.com/coolhva/usg-kpn-ftth/archive/master.zip" target="_blank" rel="noopener noreffer ">scripts from GitHub</a>.</p>
</li>
<li>
<p>Upload the <strong>config.gateway.json</strong> to the <em>unifi controller</em> (/usr/lib/unifi/data/sites/default) using SCP (I use <a href="https://winscp.net/" target="_blank" rel="noopener noreffer ">WinSCP</a>).</p>
<blockquote>
<p>Tip: If you do not see the sites/default folder, it will be created by uploading a Map in the UniFi controller web interface.</p></blockquote>
</li>
<li>
<p>Upload the <strong>dhcp6.sh</strong> and <strong>setroutes.sh</strong> to the <em>USG</em> (/home/«user») using SCP.</p>
</li>
<li>
<p>Login using SSH (I use <a href="https://www.chiark.greenend.org.uk/~sgtatham/putty/" target="_blank" rel="noopener noreffer ">PuTTY</a>) into the USG and execute the following commands:</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-bash">
        <span class="code-title"><i class="arrow fas fa-angle-right fa-fw" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h fa-fw" aria-hidden="true"></i></span>
        <span class="copy" title="Copy to clipboard"><i class="far fa-copy fa-fw" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo mv dhcp6.sh /config/scripts/post-config.d/
</span></span><span class="line"><span class="cl">sudo chmod +x /config/scripts/post-config.d/dhcp6.sh
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">sudo mv setroutes.sh /config/scripts/post-config.d/
</span></span><span class="line"><span class="cl">sudo chmod +x /config/scripts/post-config.d/setroutes.sh</span></span></code></pre></div></div>
<blockquote>
<p>KPN sends static routes via DHCP, which the USG does not install by default. This script will install the DHCP routes when a DHCP lease is received. The chmod +x command allows the script to be executed.</p></blockquote>
</li>
<li>
<p>In the UniFi controller web interface, go to the USG in devices and <strong>force provisioning</strong>.</p>
</li>
<li>
<p>After provisioning, <strong>reboot the USG</strong>. After two minutes, IPv6 will be available.</p>
</li>
</ol>
<h2 id="ssh-authentication">SSH Authentication</h2>
<p>To login using SCP and SSH, you need to enable the SSH Authentication in the UniFi controller web interface. You can find it under <em>Network Settings -&gt; Device Authentication</em>.</p>
]]></description></item><item><title>My Home and Office Network Setup</title><link>https://clemens.ms/my-home-and-office-network-setup/</link><pubDate>Mon, 18 May 2020 00:00:00 +0000</pubDate><author>Clemens Schotte</author><guid>https://clemens.ms/my-home-and-office-network-setup/</guid><description><![CDATA[<div class="featured-image">
                <img src="/featured-image.jpg" referrerpolicy="no-referrer">
            </div><p>Working from home, I use <a href="https://teams.microsoft.com/" target="_blank" rel="noopener noreffer ">Microsoft Teams</a> for online meetings. Some of my colleges and customers have an unreliable Internet connection at their homes. Most of the time, the problems are with their Wi-Fi what results in audio or video dropouts during online meetings. In this blog post, I outline my home and office network and Internet setup for a fast and rock-solid connection.</p>
<h2 id="internet">Internet</h2>
<p>In the Netherlands we are blessed with fast and affordable internet, we have options like cable, fiber, and VSDL. For my home and office, I use a 500/500 Mbit fiber connection from my local ISP (KPN). Running <a href="https://www.speedtest.net/" target="_blank" rel="noopener noreffer ">Speedtest</a> and <a href="https://fast.com/" target="_blank" rel="noopener noreffer ">Fast</a> (from Netflix) gives me a good understanding of how fast it is.</p>
<h2 id="networking-equipment">Networking equipment</h2>
<p>All my networking equipment is from <a href="https://www.ui.com/" target="_blank" rel="noopener noreffer ">Ubiquiti</a>, and I use their <a href="https://www.ui.com/products/#unifi" target="_blank" rel="noopener noreffer ">UniFi</a> product line. My choice for Ubiquiti/UniFi comes from the experience I had in the past. UniFi gives me professional control over my network and still is easy to use. You need at least a Security Gateway (USG), a Switch with PoE (Power over Ethernet), and one or more Access Points (AP) to start. The control software you can run on your computer, but I recommend to use a Cloud Key (Gen2). All my access points are getting their power from the network cable, so no need to have additional power lines. I manage my network using the Cloud Key (Gen2) Plus. An extra benefit is that it gives me lots of insides, and it is easy to operate.</p>
<p>Usually, the Internet connection comes with a modem with a built-in Wi-Fi router and is placed near the front door, which is not the best place in the house, and they far away from the office. These &lsquo;modems&rsquo; are also not the best quality and have a limited Wi-Fi range. If possible, remove this device from your network or set it in bridge mode. <a href="/kpn-fiber-connection-with-ubiquiti-usg-iptv-and-ipv6/" rel="">Read more about how</a>.</p>
<h2 id="networking-cables">Networking cables</h2>
<p>When building my house last year, I wired all my rooms and ceilings with <strong>CAT6</strong> networking cables. All networking cables come together nearby the front door where also the Internet connection (fiber) is entering the house. I created a mini networking cabinet where I have my switching and security gateway networking equipment.</p>
<p>Colors I used for the patch cables are:</p>
<ul>
<li><strong>Red</strong> Internet (public IP)</li>
<li><strong>Green</strong> Local network (firewalled)</li>
<li><strong>Yellow</strong> patch cables to devices and access points</li>
</ul>
<h2 id="access-points">Access points</h2>
<p>I have multiple access points, on every floor at least 1. The UniFi access points give me excellent Wi-Fi coverage in and around the house. Using the Map functionality in the controller software, I can check where I have blind spots in my house (Below picture is an example). All other devices are hardwired, like in the office.</p>
<h2 id="optimize-wi-fi-throughput">Optimize Wi-Fi throughput</h2>
<p>To get the maximum Wi-Fi throughput, you need to change the Channel Width. I did this only for the 5G radios, where I turned the Channel Width to VHT80 or VHT160. By default, Channel Width VHT40 is suitable for dense networks with a lot of clients, like a public place (what is not my home).</p>
]]></description></item></channel></rss>