<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

	<title>Rob's Blog</title>
	<link href="https://devopsjournal.io/blog/atom.xml" rel="self"/>
	<link href="https://devopsjournal.io/" />
	<updated>2026-05-16T18:34:17+00:00</updated>
	<id>https://devopsjournal.io/blog/</id>
	<author>
		<name>Rob Bos</name>
		<email>raj.bos+devopsjournal_io@gmail.com</email>
	</author>

	
		<entry>
			<title>AI Engineering Fluency: tracking your AI coding habits</title>
			<link href="https://devopsjournal.io/blog/2026/05/15/ai-engineering-fluency-extension"/>
			<updated>2026-05-15T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2026/05/15/ai-engineering-fluency-extension</id>
			<content type="html">&lt;p&gt;I’ve been building an extension that gives you insights into your AI usage when coding — how many tokens you consume, which models you prefer, how you structure your prompts, and what your environmental footprint looks like. It’s called &lt;a href=&quot;https://github.com/rajbos/ai-engineering-fluency&quot;&gt;AI Engineering Fluency&lt;/a&gt; and I recorded a &lt;a href=&quot;https://www.youtube.com/watch?v=Odm3wNursCY&quot;&gt;walkthrough video&lt;/a&gt; showing what it does.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2026/20260515/20260515_hero_ai-engineering-fluency.png&quot; alt=&quot;AI Engineering Fluency&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;what-it-does&quot;&gt;What it does&lt;/h2&gt;

&lt;p&gt;After installing the extension, it reads local log files from your AI editors — VS Code, Copilot CLI, Claude Code, Gemini CLI, Cursor, Crush, Mistral, and more. Everything stays on disk. Nothing leaves your machine unless you opt in to cloud sync.&lt;/p&gt;

&lt;p&gt;The extension crawls those session logs and figures out what you’ve been doing with AI on that machine. It then groups the data to give you an overview right in the status bar: today’s token usage and a 30-day rolling total.&lt;/p&gt;

&lt;h2 id=&quot;token-tracking-and-usage-charts&quot;&gt;Token tracking and usage charts&lt;/h2&gt;

&lt;p&gt;Clicking the status bar icon opens a detailed panel showing:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Total tokens used today vs. last 30 days&lt;/li&gt;
  &lt;li&gt;Number of distinct working sessions&lt;/li&gt;
  &lt;li&gt;Interactions per session and tokens per session&lt;/li&gt;
  &lt;li&gt;Breakdown by editor (VS Code, Copilot CLI, Claude Code, etc.)&lt;/li&gt;
  &lt;li&gt;Breakdown by model (Claude Opus, Claude Sonnet, Mistral, GPT, etc.)&lt;/li&gt;
  &lt;li&gt;Estimated cost over time based on token usage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/images/2026/20260515/20260515_usage-by-editor.png&quot; alt=&quot;Screenshot of the extension detail panel&quot; /&gt;&lt;/p&gt;

&lt;p&gt;On the day I recorded the walkthrough, I had already hit 23 distinct working sessions and gone through over 120 million tokens across different tools. You can view charts per day, per week, or per month. I noticed my own usage has been following a hockey stick pattern since November — it just keeps going up. The charts also let you slice by model, editor, or even repository.&lt;/p&gt;

&lt;p&gt;The dashboard also breaks activity down by mode. In my case, a lot of sessions happen in CLI tools and agent mode, but I still use ask mode for direct questions. That distinction matters because not every AI interaction is the same kind of work.&lt;/p&gt;

&lt;h2 id=&quot;multi-model-usage-insights&quot;&gt;Multi-model usage insights&lt;/h2&gt;

&lt;p&gt;One of the more interesting findings: 18% of my sessions in the last 30 days used more than one model. The maximum was five different models in a single session. I apparently switch between heavier models for planning and lighter ones for implementation — something I wasn’t fully aware of until I saw the data.&lt;/p&gt;

&lt;h2 id=&quot;environmental-impact&quot;&gt;Environmental impact&lt;/h2&gt;

&lt;p&gt;Since we have all this token data, the extension also estimates your environmental footprint. CO2 consumption, water usage, and equivalent metrics like “how far could you drive a car” or “how many smartphone charges is that.” These numbers are estimates based on public data for model/provider energy and water usage, so I treat them as directional rather than exact. The eye-opener for me was the estimated water consumption and the number of trees needed to offset my CO2 usage. There’s compute in a data center somewhere being consumed every time you send a prompt — this makes that visible.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2026/20260515/20260515_environmental-impact.png&quot; alt=&quot;Environmental impact: estimated water usage and tree equivalent&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-fluency-score&quot;&gt;The Fluency Score&lt;/h2&gt;

&lt;p&gt;The real payoff is what we call the Fluency Score. It maps your usage patterns against six aspects of AI engineering:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Prompt Engineering&lt;/strong&gt; — Do you use multi-turn sessions? Do you mix ask mode with agent mode?&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Context Engineering&lt;/strong&gt; — Are you explicitly adding context to conversations?&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Agentic&lt;/strong&gt; — Do you use agent mode and autonomous features?&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Tool Usage&lt;/strong&gt; — Are you using built-in tools and MCP servers?&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Customization&lt;/strong&gt; — Have you configured model selection and repo-level settings?&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Workflow Integration&lt;/strong&gt; — Do you use AI regularly across different modes?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each aspect has four stages (Skeptic, Explorer, Collaborator, Strategist). The score highlights where you’re strong and where you have room to grow — with links to documentation and short videos so you can learn about features you might not know exist.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2026/20260515/20260515_fluency-score.png&quot; alt=&quot;Fluency Score radar chart showing Stage 4: AI Strategist&quot; /&gt;&lt;/p&gt;

&lt;p&gt;For example, my prompt engineering is at stage four because I do multi-turn sessions with an average of 4.8 exchanges. But my context engineering could use work — I could be more explicit about adding files and symbols to conversations. The extension also tracks which files were pulled into conversations and whether I added that context myself or the editor did it for me, which makes the context engineering score feel less abstract.&lt;/p&gt;

&lt;h2 id=&quot;supported-editors&quot;&gt;Supported editors&lt;/h2&gt;

&lt;p&gt;The extension supports a wide range of tools:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;VS Code (Stable, Insiders, Exploration) + GitHub Copilot&lt;/li&gt;
  &lt;li&gt;VSCodium, Cursor, Windsurf, Trae, Kiro&lt;/li&gt;
  &lt;li&gt;JetBrains IDEs + GitHub Copilot&lt;/li&gt;
  &lt;li&gt;GitHub Copilot CLI&lt;/li&gt;
  &lt;li&gt;Claude Code (Anthropic)&lt;/li&gt;
  &lt;li&gt;Gemini CLI (Google)&lt;/li&gt;
  &lt;li&gt;Mistral Vibe&lt;/li&gt;
  &lt;li&gt;OpenCode, Crush, Continue&lt;/li&gt;
  &lt;li&gt;Visual Studio 2022+&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And has viewers for:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Visual Studio Code (and thus Codium based editors like Cursor, Windsurf, Trae, Kiro)&lt;/li&gt;
  &lt;li&gt;Visual Studio 2022+&lt;/li&gt;
  &lt;li&gt;JetBrains IDEs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And even a CLI version for quick stats in the terminal. If you use any of these tools, you can get insights into your AI usage right away.&lt;/p&gt;

&lt;h2 id=&quot;getting-started&quot;&gt;Getting started&lt;/h2&gt;

&lt;p&gt;Install it from the &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=RobBos.ai-engineering-fluency&quot;&gt;VS Code Marketplace&lt;/a&gt; or the &lt;a href=&quot;https://plugins.jetbrains.com/plugin/31580-ai-engineering-fluency&quot;&gt;JetBrains Marketplace&lt;/a&gt;. There’s also a CLI version available via npm:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npx @rajbos/ai-engineering-fluency stats
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It’s free, open source, and all your data stays local. The repo is at &lt;a href=&quot;https://github.com/rajbos/ai-engineering-fluency&quot;&gt;github.com/rajbos/ai-engineering-fluency&lt;/a&gt; — contributions and feature requests are welcome through the repo as well.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>The AI subsidy era is ending: time to talk business value</title>
			<link href="https://devopsjournal.io/blog/2026/05/15/ai-billing-business-value"/>
			<updated>2026-05-15T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2026/05/15/ai-billing-business-value</id>
			<content type="html">&lt;p&gt;GitHub Copilot &lt;a href=&quot;https://github.blog/news-insights/company-news/github-copilot-is-moving-to-usage-based-billing/&quot;&gt;moves to usage-based billing on June 1&lt;/a&gt;. We have already seen customers that will see their AI spend on Copilot go to 2x or 3x (median), with some even going 8x! And this is happening across all model hosters and vendors: Anthropic has been pushing people onto their Max tiers ($200/month) and metered Claude Code usage. Gemini, Cursor, Windsurf, and the rest are doing the same math and arriving at the same answer. The era where AI was heavily subsidised by vendors trying to grab market share is ending, and now we get to pay the actual price of hosting all those GPUs.&lt;/p&gt;

&lt;p&gt;Some companies are about to wake up and smell the roses.&lt;/p&gt;

&lt;p&gt;Some reference stories of the last days:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Sander Trijssenaar wrote a &lt;a href=&quot;https://www.trijssenaar.net/blog/2026-05-15-new-github-copilot-app-token-based-billing-major-ai-spend-increase/&quot;&gt;solid breakdown of the new GitHub Copilot App combined with token billing&lt;/a&gt; and what that means for spend.&lt;/li&gt;
  &lt;li&gt;Hidde de Smet did the &lt;a href=&quot;https://hiddedesmet.com/the-real-cost-of-ai-coding-agents&quot;&gt;per-token math across every major provider&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;State of Brand called the whole subsidy model &lt;a href=&quot;https://www.thestateofbrand.com/news/ai-subscription-time-bomb&quot;&gt;a ticking time bomb&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;AJ Enns put together a &lt;a href=&quot;https://github.com/aj-enns/token-economy&quot;&gt;token economy playbook&lt;/a&gt; for keeping spend under control once the meter is running.&lt;/li&gt;
  &lt;li&gt;I see the same thing happening in the &lt;a href=&quot;https://discord.gg/cURHV9TvFS&quot;&gt;Copilot Discord&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Read all four references if you haven’t — they cover the numbers better than I will here.&lt;/p&gt;

&lt;p&gt;What I want to talk about is what comes after the panic.&lt;/p&gt;

&lt;h2 id=&quot;the-trough-were-sliding-into&quot;&gt;The trough we’re sliding into&lt;/h2&gt;

&lt;p&gt;If you look at 
&lt;a href=&quot;https://www.gartner.com/en/newsroom/press-releases/2025-08-05-gartner-hype-cycle-identifies-top-ai-innovations-in-2025&quot;&gt;Gartner Hype Cycle&lt;/a&gt;, this is the bit right after “Peak of Inflated Expectations” — the slide into the Trough of Disillusionment. We’re sliding into it right now. Here is last years Hype Cycle for reference, althoug this is on AI overall:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2026/20260515/GartnerHypeCycle-et-hc-2025-press-release.jpg&quot; alt=&quot;The Gartner Hype Cycle, showing the &amp;quot;Trough of Disillusionment&amp;quot; phase after the &amp;quot;Peak of Inflated Expectations&amp;quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The negative sentiment on pricing is everywhere on LinkedIn, Mastodon, Reddit, and every dev community in between: individual users hopping between editors and CLIs trying to find the next thing that runs AI for free or close to it, and companies scrambling to get costs under control by limiting AI use instead of enabling their engineers.&lt;/p&gt;

&lt;p&gt;That second part is the wrong path. Restricting AI to control the bill saves a few thousand euros a month and leaves a multiple of that in business value on the table. I see it in the numbers all the time.&lt;/p&gt;

&lt;h2 id=&quot;what-the-numbers-actually-look-like&quot;&gt;What the numbers actually look like&lt;/h2&gt;

&lt;p&gt;At Xebia we’ve built a &lt;a href=&quot;https://copilot-billing.xebia.ms/&quot;&gt;Copilot billing dashboard&lt;/a&gt; on top of the standard GitHub data, with extra logic that flags things like the Business → Enterprise upgrade math during the current promo period. The pattern that keeps showing up: a small group of heavy users carries most of the cost, and a much larger group is barely touching their allotment.&lt;/p&gt;

&lt;p&gt;One example from a real customer: four users were responsible for roughly 50% of the company-wide bill. The rest of the org has AI Credits sitting unused every month. That’s not a cost problem — that’s a massive opportunity problem. Those underused seats are engineers who could be shipping faster, fixing tech debt, or building MVPs to test new ideas, and instead they’re using maybe a third of what they’re already paying for. Some companies approach giving out licenses the wrong way. I have shared my views on this at conferences left and right:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;2024 - GitHub Universe: &lt;a href=&quot;https://youtu.be/eJRNJwlLFts&quot;&gt;Lessons learned from enabling thousands of developers on GitHub Copilot&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The AI Credits pooling model under the new billing for GitHub Copilot helps with this on paper — heavy users draw from the shared pool, light users effectively subsidise them. But it also masks the real story: a lot of seats are paid for and not used. That’s the gap I want teams to close, with data-driven insights and proactive management.&lt;/p&gt;

&lt;h2 id=&quot;my-own-usage-hockey-stick&quot;&gt;My own usage hockey stick&lt;/h2&gt;

&lt;p&gt;I’ve been tracking my own AI usage in detail for a while with this &lt;a href=&quot;http://github.com/rajbos/ai-engineering-fluency&quot;&gt;VS Code Extension&lt;/a&gt; I built. Here’s what it what my hockey stick growth looks like:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2026/20260515/20260515_Hockeystick.png&quot; alt=&quot;Token usage and sessions per month from June 2025 to May 2026, climbing from near zero through October to a projected ~1.8B tokens in May 2026&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Near zero through October 2025. A small bump in November and December. Then it climbs through Q1 2026 and goes vertical in March and April. April hit ~530 sessions and ~1.4B tokens. May is projected around 1.8B tokens. And to be clear: I’m not running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/fleet&lt;/code&gt;, Squad, or any other multi-agent army. This is one person, working interactively most of the time.&lt;/p&gt;

&lt;p&gt;I vividly remember end of March where I hit my first 100 Million tokens in 30 days. I thought “oh, that’s a lot!”. Then I hit 1 Billion tokens just 3 weeks later. Let that sync in: from 100M to 1B in three weeks.&lt;/p&gt;

&lt;p&gt;What changed? Two things. First, agent mode genuinely became my default for non-trivial work after December. Second, I got early access to the GitHub Copilot App that &lt;a href=&quot;/blog/2026/05/14/github-copilot-app&quot;&gt;went into technical preview in May 2026&lt;/a&gt;, and it is by design a more agentic experience which of course consumes more tokens: parallel sessions, longer autonomous runs, Agent Merge handling CI failures, merge conflicts, and security review comments, all on its own. I wrote about that release the day it dropped &lt;a href=&quot;/blog/2026/05/14/github-copilot-app&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So yes, my AI bill will go up significantly. The question I want to talk about is whether that’s worth it and how I look at it.&lt;/p&gt;

&lt;h2 id=&quot;a-concrete-case-57-in-tokens-for-a-jetbrains-extension&quot;&gt;A concrete case: $57 in tokens for a JetBrains extension&lt;/h2&gt;

&lt;p&gt;Here’s a session from my own custom dashboard that shows costs per session:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2026/20260515/20260515_SessionCosts.png&quot; alt=&quot;Session efficiency view showing a single JetBrains extension session at $57.05, 54 user turns, 30 commits, 31 files edited, with token usage across Claude Opus 4.7, Opus 4.6, Haiku 4.5, Sonnet 4.6 and GPT-5 mini&quot; /&gt;&lt;/p&gt;

&lt;p&gt;One session. A couple of hours of my time. $57.05 in AI spend across Claude Opus 4.7, Sonnet 4.6, Haiku 4.5, and GPT-5 mini. Output: a working JetBrains extension — &lt;a href=&quot;https://github.com/rajbos/ai-engineering-fluency&quot;&gt;rajbos/ai-engineering-fluency&lt;/a&gt; — that visualises in-depth information about your AI usage: sessions, tokens, chat turns, models used, and even an AI fluency score. Same kind of insights I’d been building for VS Code and Visual Studio, now ported to the JetBrains ecosystem.&lt;/p&gt;

&lt;p&gt;If I had to build that from scratch, learning the JetBrains plugin SDK along the way, I’d estimate two weeks of work. Maybe more. Instead it cost me a couple of hours and $57 in tokens.&lt;/p&gt;

&lt;p&gt;Was it worth it? An engineer’s hours for two weeks versus a few hours plus $57 — that’s not even a close call. The business case writes itself.&lt;/p&gt;

&lt;p&gt;That’s the conversation we need to be having about every meaningful AI session: not “how much did the tokens cost” in isolation, but “what did we get for it, and what would the alternative have cost”. And of course, just raw cost and “did you ship anything” is not the right way to look at it either. The real question is “what was the business value of the session overall, and was that worth it”. Tracking these sessions to something sensible or tangible is hard! When I start research in session one, and then hop over to another tool, tagging those sessions together is unrealistic. But we can get close enough to have a meaningful conversation about the value of the work done in those sessions, and that’s what we should be doing. Also the other side is true: shipping more crap and bugs into production faster is also not a good business case. The value conversation has to be about the overall impact of the session in the long term, not just the singular output or the cost.&lt;/p&gt;

&lt;p&gt;If a beefy session shaves off 2ms in a request on a system that does millions a day, that’s a huge business win. If it produces a feature that customers love and drives more revenue, that’s a huge business win. If it refactors a piece of code that was causing 10% of the bugs in production, that’s a huge business win. If it just produces a few more lines of code that don’t actually move the needle, then maybe it’s not worth it.&lt;/p&gt;

&lt;p&gt;If a session teaches a new developer on the team how to do something they didn’t know before, that’s a huge business win. If it helps an experienced developer get unstuck on a problem that was blocking them for days, that’s a huge business win. If it just produces a few more lines of code that don’t actually move the needle, then maybe it’s not worth it.&lt;/p&gt;

&lt;h2 id=&quot;foundation-first-then-speed&quot;&gt;Foundation first, then speed&lt;/h2&gt;

&lt;p&gt;Here’s where it ties back to something I’ve been saying for over a year. Back in April 2025 I wrote &lt;a href=&quot;/blog/2025/04/01/GitHub-Copilot-Change-the-Narrative&quot;&gt;GitHub Copilot — Change the Narrative&lt;/a&gt;, arguing that the productivity framing for AI was the wrong one. The real value is that AI gives engineers time to put a sturdy DevOps foundation in place: automated pipelines, testing you actually trust, monitoring, the more-eyes principle, everything-as-code.&lt;/p&gt;

&lt;p&gt;That argument is more relevant now than it was then. With usage-based billing, every team has to justify what their AI spend produces. And all of a sudden we will have a (justified) conversation again about the AI cost for that team. The teams that can answer “we used the time to get our pipelines, tests, and monitoring in shape, and now we ship features in days instead of weeks with confidence they won’t break production” are going to look very different from the teams that just shipped more features faster on top of a wobbly foundation.&lt;/p&gt;

&lt;p&gt;Enabling the teams to get the most out of their AI usage is the job of engineering leadership. They need to give the team the time to absorb the new tools, tailor the AI setup to their stck and way of working, and then some. So many businesses make the mistake of handing out licenses and hope that they get 10x engineers. That’s not how any of this works!&lt;/p&gt;

&lt;p&gt;Instead, spend the early time with the team on the foundation of their SDCLC setup. Get your confidence up to the point where you can actually go faster. Then go even faster.&lt;/p&gt;

&lt;p&gt;Once that foundation is solid, tackling the technical debt backlog stops being a luxury item. AI sessions are very good at the kind of bounded, well-tested refactor work that usually sits at the bottom of the backlog forever. The cost of a refactor session is small. The cost of carrying that debt for another year, in slower delivery and more bugs, is much larger.&lt;/p&gt;

&lt;h2 id=&quot;then-the-real-prize-speed-of-opportunity&quot;&gt;Then the real prize: speed of opportunity&lt;/h2&gt;

&lt;p&gt;Once foundation and tech debt are in a decent state, the next conversation is the one that actually matters to the business: cost of opportunity.&lt;/p&gt;

&lt;p&gt;A team that’s fluent with AI in their SDLC, on top of a solid DevOps foundation, can turn on a dime. New feature idea on Monday, MVP in the user’s hands by Friday. That speed of response is the business value. Not “engineers type faster”. Not “we shipped 30% more story points”. The ability to react to a market signal or a customer request in days instead of months is what AI in the SDLC actually unlocks, and it’s what should be in the business case.&lt;/p&gt;

&lt;p&gt;So the conversation with the business becomes: here’s the change, here’s the AI spend (training engineers, tokens, compute) it took to get it out, is the value worth that cost? My honest expectation is that the answer is an immediate yes most of the time. For the cases where it isn’t obvious, spin up an AI session to do the research and build a quick MVP — that’s trivial these days, and it’s usually enough to make a real business case from.&lt;/p&gt;

&lt;h2 id=&quot;look-at-the-current-spent-to-avoid-surprises-then-shift-the-conversation-to-business-value&quot;&gt;Look at the current spent to avoid surprises, then shift the conversation to business value&lt;/h2&gt;

&lt;p&gt;Here are few concrete things, in roughly the order I’d tackle them:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Get visibility into your usage before June 1. The GitHub preview bill is the bare minimum. If you want more, the &lt;a href=&quot;https://copilot-billing.xebia.ms/&quot;&gt;Xebia Copilot billing dashboard&lt;/a&gt; is one option (we even share opportunities to upgrade Business licenses to Enterprise licenses based on the data), or look at the &lt;a href=&quot;https://github.com/aj-enns/token-economy&quot;&gt;token economy playbook&lt;/a&gt; as it is another good starting point.&lt;/li&gt;
  &lt;li&gt;Look at who is and isn’t using their allotment. The underusers are not a saving — they’re an opportunity. Find out why they’re not using it (no training, no use case, fear of “doing it wrong”) and fix that. Talk to the engineers and see what holds them back. I’ve seen teams with editors out of 2023, teams on hosted or locked down editors that did not allow them to install new tools and extension, or teams that kept on saying “the AI does not get our stack”. All of those are solvable problems, and the solution is not “just don’t use AI then”. It’s “let’s fix the problem so you can use AI and be more effective”. Call us if you need help in this field!&lt;/li&gt;
  &lt;li&gt;Run the upgrade math during the promo period. For some users, moving from Business to Enterprise pays for itself thanks to the bigger included allotment, and the surplus credits help cover the heavy users without overage.&lt;/li&gt;
  &lt;li&gt;Stop measuring AI by “engineers shipping faster”. Start measuring it by what you spent the freed time on: foundation work, tech debt, MVPs that became real features, deep research that lead to improvements. I am a fan of the book &lt;a href=&quot;https://www.amazon.nl/-/en/Nicole-Forsgren/dp/1662966377/ref=sr_1_1?adgrpid=1341406728955258&amp;amp;dib=eyJ2IjoiMSJ9.r4fbGSDpzlF1M-6oEq0Stynwnl9ObpQ45b9f0FcMleR0eUzrWPeM9M-tkOlGGSOmwmlzq9nuSbMlDPrfuZ5f-3uQX_CfnKiFIJINd2t3rknrT1_9gm8ofybsFMO8NhYOvvnMV2q0BT0wYS5kGHQ5WJvMINgaT-Js6CnKsu03aYqgmq647_cPz2s9fucjK55e3tI2OBYfZaYV5amMIB_C2KsOisKAcKOx-gN7L2p8baJpd6Bs-7Kr8tHDB2XH2sti5L0NzDgcE2CUhKz_VDpq7MGTL_VrYaY1PB878Ko2KW0.1l11q18OYBoDeKvq8wlaCssWM0hq1FVGJQRBjkz3Chc&amp;amp;dib_tag=se&amp;amp;hvadid=83838200192813&amp;amp;hvbmt=be&amp;amp;hvdev=c&amp;amp;hvlocphy=152248&amp;amp;hvnetw=o&amp;amp;hvqmt=e&amp;amp;hvtargid=kwd-83839127493302%3Aloc-129&amp;amp;hydadcr=24320_2296430&amp;amp;keywords=frictionless+book&amp;amp;mcid=dcf0dce74e943256a8ca6e59aea0ba68&amp;amp;msclkid=7a35294523b513a4ea3e434218f5df5b&amp;amp;qid=1778858818&amp;amp;sr=8-1&quot;&gt;Frictionless&lt;/a&gt; from Abi Noda and Nicole Forsgren, which has a great framework for measuring the value of work in terms of “friction removed”, as well as a way to explain this in business value. I think that is a much better way to look at the value of AI sessions than “how many lines of code did we ship”.&lt;/li&gt;
  &lt;li&gt;Have the business value conversation per change, not per seat. Tie AI spend to the feature it produced, then ask the business if it was worth it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you want to talk about any of this, reach out to me on &lt;a href=&quot;https://www.linkedin.com/in/bosrob&quot;&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The subsidy era ending isn’t a bad thing. It forces the conversation we should have been having from day one: what is this actually worth?&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>GitHub Copilot App is now in Technical Preview</title>
			<link href="https://devopsjournal.io/blog/2026/05/14/github-copilot-app"/>
			<updated>2026-05-14T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2026/05/14/github-copilot-app</id>
			<content type="html">&lt;p&gt;Today GitHub released the &lt;a href=&quot;https://docs.github.com/en/copilot/concepts/agents/github-copilot-app&quot;&gt;GitHub Copilot App&lt;/a&gt; into technical preview. I’ve been using it as my daily driver for a while now, so I started a video series to share what I’ve learned. This post covers the intro to the app and a first look at using it for repository maintenance.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2026/20260514/TheGitHubApp.png&quot; alt=&quot;GitHub Copilot App&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The app is a standalone desktop experience — available on Windows, macOS, and Linux — built specifically for agent-driven development. The big idea is that all the context you’re working with already lives in GitHub, so the app starts there. Instead of context-switching between your terminal, IDE, and browser, you do all of that in one place.&lt;/p&gt;

&lt;p&gt;A few things that stand out from the &lt;a href=&quot;https://github.blog/changelog/2026-05-14-github-copilot-app-is-now-available-in-technical-preview/&quot;&gt;changelog announcement&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Parallel sessions — each session gets its own git work tree, branch, and task state, so you can run multiple agents against the same repo without them stepping on each other&lt;/li&gt;
  &lt;li&gt;Three session modes: Interactive (you collaborate step by step), Plan (agent proposes, you approve), or Autopilot (fully autonomous)&lt;/li&gt;
  &lt;li&gt;A GitHub inbox showing open issues and PRs across connected repos, so you can start a session directly from real work that’s waiting&lt;/li&gt;
  &lt;li&gt;Agent Merge — the agent watches CI checks, downloads failure logs, resolves merge conflicts, and pushes fixes automatically before merging (more on this below)&lt;/li&gt;
  &lt;li&gt;Model choice per session, including reasoning effort tuning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Out of this, Agent Merge is my favorite feature! It will fix merge conflicts, CI failures, Security Alerts, and other common issues that pop up during the PR lifecycle, without you having to lift a finger. Saves a lot of manual work and context switching, and it just works. I have a video below where I show it in action.&lt;/p&gt;

&lt;p&gt;To get access right now:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Copilot Pro and Pro+: &lt;a href=&quot;https://gh.io/github-copilot-app&quot;&gt;join the waitlist&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Copilot Business and Enterprise: available this week once your org admin has enabled preview features and the Copilot CLI in policy settings&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;first-look-introducing-the-app&quot;&gt;First look: introducing the app&lt;/h2&gt;

&lt;p&gt;In the first video I walk through what the app actually looks like and how a typical session flows — picking an issue from the inbox, watching the agent plan and implement it, then Agent Merge handling the PR without any babysitting from me.&lt;/p&gt;

&lt;p&gt;The app uses git work trees under the hood. Each session lives in its own work tree, so three concurrent sessions against the same repo just work — no branch conflicts, no waiting.&lt;/p&gt;

&lt;p&gt;The commit signing moment in the video is a real one: the agent ran into the GPG passphrase prompt, flagged it, and waited for me to type it in before continuing. Small thing, but it shows that the interactive mode is genuinely interactive rather than just autopilot with a pause button.&lt;/p&gt;

&lt;iframe width=&quot;900&quot; height=&quot;506&quot; src=&quot;https://www.youtube.com/embed/czAIWMCfkPY&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;using-it-for-repo-maintenance&quot;&gt;Using it for repo maintenance&lt;/h2&gt;

&lt;p&gt;Once you get past the intro, the maintenance use case is where this really starts to pay off. In the second video I take a repo with a backlog of open issues and a pile of Dependabot PRs, and use the app to triage and action them without writing a single line of code myself.&lt;/p&gt;

&lt;p&gt;Starting from a natural language prompt like “triage open PRs and issues from a maintainer perspective” kicks off the repo status agent, which pulls in Dependabot vulnerability alerts alongside the regular issues and PRs. It then surfaces what’s safe to merge, what needs a look, and what’s been sitting untouched. From there you can tell it to kick off sub-agents for each issue in parallel — separate work trees, separate PRs, all running concurrently while you go deal with something else.&lt;/p&gt;

&lt;p&gt;A tip that’s easy to miss: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+K&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Cmd+K&lt;/code&gt; opens a command palette that lets you search all your recent sessions, jump to specific ones, and see their current status. Handy once you have more than a handful of sessions in flight.&lt;/p&gt;

&lt;iframe width=&quot;900&quot; height=&quot;506&quot; src=&quot;https://www.youtube.com/embed/v8xa5ZuRlR8&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;To give a sense of boosts this app can give an engineer: you can find my contributions on a page (created with the GitHub Copilot App ofc): &lt;a href=&quot;https://devex-metrics.github.io/devex-metrics/&quot;&gt;devex-metrics.github.io/devex-metrics&lt;/a&gt; where you can find 368 PR’s MERGED in the last 30 days. Just on OSS contributions (mostly my own projects).&lt;/p&gt;

&lt;p&gt;In the image below you can find an overview of 2 days of sessions I have worked on with the App (and yes, I still work in the CLI and VS Code as well, although way less, might be only 5% outside of the App).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2026/20260514/20260514_sessions_overview.png&quot; alt=&quot;Overview of 2 days of sessions in the GitHub Copilot App&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Amazing to see what a person can do and achieve these days!&lt;/p&gt;

&lt;p&gt;I’ll keep adding videos to the series as I go deeper. Drop a comment on either video if there’s something specific you want me to cover or ping me on &lt;a href=&quot;https://www.linkedin.com/in/bosrob/&quot;&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Shooting yourself in the foot with AI</title>
			<link href="https://devopsjournal.io/blog/2026/05/08/Copilot-mishap"/>
			<updated>2026-05-08T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2026/05/08/Copilot-mishap</id>
			<content type="html">&lt;p&gt;Today I had a fun one, where I finally dove into something that was bothering me for days: since a couple of days I had been getting questions from my different GitHub Copilot sessions to close some GitHub nofications, and only from a very specific type of notification: deployment statuses.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2026/20260508/20260508_NotificationMessages.png&quot; alt=&quot;Screenshot of the AI asking to cleanup notifications&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I noticed I got these accross different projects, so I was tempted to blame the use of a new tool that recently got updated for this. I checked it low and wide on configuration, user system prompts, etc., even checked my copilot-instructions files, but nothing to be found. Already logged an error with the tool creator, thinking it was something they added without telling me.&lt;/p&gt;

&lt;h1 id=&quot;figuring-out-where-this-was-coming-from&quot;&gt;Figuring out where this was coming from&lt;/h1&gt;
&lt;p&gt;Of course I dropped into a Copilot CLI session to figure this one out. Somewhere, in one of my config files, either on the repo  or global level, this could have been added somewhere? Perhals a tool config, VS Code extension, or something else?&lt;/p&gt;

&lt;p&gt;After checking a couple of these locations, Copilot found the issue: 
&lt;img src=&quot;/images/2026/20260508/20260508_ExtensionFound.png&quot; alt=&quot;Screenshot of the AI finding the issue&quot; /&gt;&lt;/p&gt;

&lt;p&gt;So this was created in one of my sessions by myself (with Copilot), where had interpreted a command to become: write a prompt to disk to get notifications, as an extension for the Copilot CLI! I have been working on my own Agent that would look at my GitHub notifications and clean them up for me, so this must have flowed over into a tool (extension) somewhere :smile:&lt;/p&gt;

&lt;p&gt;Cleaned it up and of course the behaviour is now gone! Happy days.
So just an example of how easy it is to shoot yourself in the foot with AI. I take some pride in looking at the changes and what it does, and having a sense of what is happening in my sessions, but this one slipped through the cracks. I guess that’s what happens when you have a lot of different tools and sessions running at the same time, and you start to delegate more and more to AI assistants. It’s a good reminder to always check what your AI is doing, and to be careful with the commands you give it!&lt;/p&gt;

&lt;h2 id=&quot;side-note-copilot-cli-extensions&quot;&gt;Side note: Copilot CLI extensions!&lt;/h2&gt;

&lt;p&gt;This whole thing now lead me to look at Copilot CLI extensions, which I had heard about before. There is some explanaion on the GitHub docs on &lt;a href=&quot;https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/plugins-finding-installing&quot;&gt;installing plugins&lt;/a&gt;, but not really how that extension ecosystem is loaded. I found a great blogpost that goes into the details of how to create your own extensions, and how they are loaded by the Copilot CLI, which is really interesting. You can find it here: &lt;a href=&quot;https://htek.dev/articles/github-copilot-cli-extensions-complete-guide/&quot;&gt;GitHub Copilot CLI Extensions: The Most Powerful Feature Nobody’s Talking About&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Have fun discovering that!&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>My Open Source Projects</title>
			<link href="https://devopsjournal.io/blog/2026/05/06/OSS-projects"/>
			<updated>2026-05-06T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2026/05/06/OSS-projects</id>
			<content type="html">&lt;p&gt;Over the past few years I’ve been building a collection of open source tools focused on GitHub, DevOps automation, and more recently AI tooling. Some have hundreds of dependents, others are still in the planning phase. I wanted to share an overview of these projects in one place, both to highlight the work I’ve been doing and to hopefully inspire others to contribute or build their own tools in this space. Here’s a quick rundown of each project, with links to the repos and a brief description of what they do. Feedback / ideas / and submissions are always welcome!&lt;/p&gt;

&lt;h2 id=&quot;devex-metrics&quot;&gt;DevEx Metrics&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href=&quot;https://github.com/devex-metrics/devex-metrics&quot;&gt;devex-metrics/devex-metrics&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;DevEx Metrics is a developer experience reporting and dashboarding tool for GitHub organisations and users. It collects a broad set of metrics — open, merged and closed pull requests, lines of code changed, comments and commits per PR, estimated GitHub Actions minutes consumed, unique committers and reviewers over the last 90 days, and dependent repository counts — and turns them into a Markdown report and an HTML dashboard you can host on GitHub Pages.&lt;/p&gt;

&lt;p&gt;You can run it locally or schedule it as a GitHub Actions workflow for daily snapshots. Both Personal Access Token and GitHub App authentication are supported, and results are JSON-cached so you’re not hammering the API on every run.&lt;/p&gt;

&lt;p&gt;The live dashboard is available with my own OSS data at &lt;a href=&quot;https://devex-metrics.github.io/devex-metrics&quot;&gt;devex-metrics.github.io/devex-metrics&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2026/20260506/DevExMetrics.png&quot; alt=&quot;Screenshot of the output site&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;ai-engineering-fluency&quot;&gt;AI Engineering Fluency&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href=&quot;https://github.com/rajbos/ai-engineering-fluency&quot;&gt;rajbos/ai-engineering-fluency&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Previously known as the “GitHub Copilot Token Tracker”, AI Engineering Fluency helps you track your AI token usage and fluency score across a lof of different AI coding tool out there: VS Code + GitHub Copilot, JetBrains IDEs, Visual Studio, Claude Code, Gemini CLI, Cursor, Continue, and more. All data is read from &lt;strong&gt;local session logs&lt;/strong&gt; — nothing leaves your machine unless you explicitly opt in.&lt;/p&gt;

&lt;p&gt;The extension shows near real-time token usage in the status bar alongside a fluency score dashboard. There’s also a JetBrains plugin, a Visual Studio extension, and a CLI (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npx @rajbos/ai-engineering-fluency stats&lt;/code&gt;) for headless environments. Teams who want to aggregate data across engineers can spin up the self-hosted sharing server — a Docker + SQLite container that authenticates via GitHub sessions, with no Azure account required.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2026/20260506/AIEngineeringFluency-Transparent.png&quot; alt=&quot;Project logo&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;alternative-github-actions-marketplace&quot;&gt;Alternative GitHub Actions Marketplace&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href=&quot;https://github.com/devops-actions/alternative-github-actions-marketplace&quot;&gt;devops-actions/alternative-github-actions-marketplace&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The official GitHub Actions Marketplace is functional but limited — no meaningful filtering, no dependents count, no easy way to evaluate community adoption. This project is my attempt at a better version.&lt;/p&gt;

&lt;p&gt;It’s a React frontend backed by an Azure Functions API and Azure Table Storage, indexing 20,000+ GitHub Actions with search by name or owner, filtering by action type (Node/JavaScript, Docker, Composite), and showing the &lt;strong&gt;dependents count&lt;/strong&gt; for each action so you can actually judge real-world adoption. Stack is Azure Static Web Apps (free tier) for the frontend, Azure Functions on a consumption plan for the API, and a companion npm package (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@devops-actions/actions-marketplace-client&lt;/code&gt;) to push metadata into the store.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2026/20260506/AlternativeMarketplace.png&quot; alt=&quot;Screenshot of the marketplace&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;devops-actions-organisation&quot;&gt;devops-actions Organisation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Org:&lt;/strong&gt; &lt;a href=&quot;https://github.com/devops-actions&quot;&gt;github.com/devops-actions&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The devops-actions organisation groups a set of GitHub Actions I’ve built for common CI/CD automation tasks. All actions follow supply-chain security best practices and are tracked with OpenSSF Scorecards.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Action&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Dependents&lt;/th&gt;
      &lt;th&gt;What it does&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/devops-actions/action-get-tag&quot;&gt;action-get-tag&lt;/a&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;679 ⭐&lt;/td&gt;
      &lt;td&gt;Read the current Git tag during a workflow run&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/devops-actions/actionlint&quot;&gt;actionlint&lt;/a&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;319 ⭐&lt;/td&gt;
      &lt;td&gt;Lint your GitHub Actions workflow files with actionlint&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/devops-actions/issue-comment-tag&quot;&gt;issue-comment-tag&lt;/a&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;196&lt;/td&gt;
      &lt;td&gt;Create or update a tagged comment on an issue or PR&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/devops-actions/variable-substitution&quot;&gt;variable-substitution&lt;/a&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;73&lt;/td&gt;
      &lt;td&gt;Substitute environment variables into config files&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/devops-actions/json-to-file&quot;&gt;json-to-file&lt;/a&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;58&lt;/td&gt;
      &lt;td&gt;Write JSON content to a file in the workspace&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/devops-actions/load-used-actions&quot;&gt;load-used-actions&lt;/a&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;18&lt;/td&gt;
      &lt;td&gt;Scan a repo or org for all GitHub Actions in use&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/devops-actions/load-available-actions&quot;&gt;load-available-actions&lt;/a&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;17&lt;/td&gt;
      &lt;td&gt;List all public actions available in an org or user account&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/devops-actions/azure-appservice-settings&quot;&gt;azure-appservice-settings&lt;/a&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;6&lt;/td&gt;
      &lt;td&gt;Push settings to an Azure App Service from a workflow&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/devops-actions/load-runner-info&quot;&gt;load-runner-info&lt;/a&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;5&lt;/td&gt;
      &lt;td&gt;Retrieve runner details (labels, OS, status) for an org&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/devops-actions/github-copilot-pr-analysis&quot;&gt;github-copilot-pr-analysis&lt;/a&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;2&lt;/td&gt;
      &lt;td&gt;Run GitHub Copilot PR analysis as a workflow step&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/devops-actions/load-dependents-count&quot;&gt;load-dependents-count&lt;/a&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1&lt;/td&gt;
      &lt;td&gt;Count how many repos depend on a given action or package&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/devops-actions/secure-action-inputs&quot;&gt;secure-action-inputs&lt;/a&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0&lt;/td&gt;
      &lt;td&gt;Validate and sanitise inputs passed to an action&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The two most widely adopted are &lt;strong&gt;action-get-tag&lt;/strong&gt; (679 dependents) — a simple utility for reading the current Git tag inside a workflow — and &lt;strong&gt;actionlint&lt;/strong&gt; (319 dependents), which wraps the actionlint tool to lint your workflow YAML files in CI.&lt;/p&gt;

&lt;h2 id=&quot;ghazdowidget&quot;&gt;GHAzDoWidget&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href=&quot;https://github.com/rajbos/GHAzDo-widget&quot;&gt;rajbos/GHAzDo-widget&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GHAzDoWidget is an Azure DevOps extension that shows GitHub Advanced Security alert counts directly in your Azure DevOps dashboards. If your source code lives in GitHub but your project management lives in Azure DevOps, this gets those alert totals — Dependabot, secret scanning, and code scanning — in front of you without having to switch context.&lt;/p&gt;

&lt;p&gt;The extension ships in multiple widget sizes: a 2×1 combined overview, individual 1×1 tiles per alert type, and larger 2×2–4×2 widgets with trend lines, severity pie charts, open/dismissed/fixed status timelines, grouped-by-repo views, and a time-to-close scatterplot to visualise how quickly alerts get resolved.&lt;/p&gt;

&lt;p&gt;Available on the &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=RobBos.GHAzDoWidget&quot;&gt;Azure DevOps Marketplace&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2026/20260506/GHAzDoWidget.png&quot; alt=&quot;GHAzDoWidget overview showing combined alert counts per type&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;jarvis&quot;&gt;Jarvis&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href=&quot;https://github.com/rajbos/Jarvis&quot;&gt;rajbos/Jarvis&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Jarvis is my personal, locally-hosted AI assistant — an Electron + TypeScript desktop app that lives in the system tray, starts automatically on boot, and sends all prompts to a local Ollama instance so nothing leaves my machine. New capabilities come in via MCP (Model Context Protocol) servers without needing to touch the core app.&lt;/p&gt;

&lt;p&gt;The planned feature set reflects my own GitHub-heavy day-to-day:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Guided onboarding to discover local Ollama models and connect GitHub via OAuth&lt;/li&gt;
  &lt;li&gt;Automated GitHub maintenance tasks (secrets scanning, fork analysis, stale repo detection, dependency audits)&lt;/li&gt;
  &lt;li&gt;Cross-repo activity tracking with weekly summary generation&lt;/li&gt;
  &lt;li&gt;Async background jobs with rate-limit awareness&lt;/li&gt;
  &lt;li&gt;Encrypted local SQLite store for config and conversation history&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The project is currently in the &lt;strong&gt;first phases&lt;/strong&gt; — the full architecture specification is in the repo if you want to follow along or contribute ideas.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2026/20260506/Jarvis.png&quot; alt=&quot;Screenshot of Jarvis&apos; repo dashboard&quot; /&gt;&lt;/p&gt;

</content>
		</entry>
	
		<entry>
			<title>Where the GitHub Copilot extension points break governance</title>
			<link href="https://devopsjournal.io/blog/2026/05/01/Copilot-extension-governance-concerns"/>
			<updated>2026-05-01T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2026/05/01/Copilot-extension-governance-concerns</id>
			<content type="html">&lt;p&gt;A lot of the recent additions to the GitHub Copilot ecosystem add real value for individual developers, yet they also expand the security surface that an enterprise has to reason about. Most of these new entry points let a developer pull executable instructions, configuration, or full processes from any random repository on the internet, with very little or no central control. This post looks at the five places where I think the gap between “useful for one engineer” and “safe to run across a 5,000 person org” is widest right now.&lt;/p&gt;

&lt;p&gt;We’ll look at these topis:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;GitHub Copilot CLI plugin marketplace&lt;/li&gt;
  &lt;li&gt;GitHub Copilot CLI local extensions&lt;/li&gt;
  &lt;li&gt;Agent Package Manager (APM)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gh skill&lt;/code&gt; now in the GitHub CLI&lt;/li&gt;
  &lt;li&gt;MCP servers across editors&lt;/li&gt;
  &lt;li&gt;VS Code extensions and the different registries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/images/2026/20260501/samuel-regan-asante-STDn0DxY8os-unsplash.jpg&quot; alt=&quot;Samuel Regan Asante from Unsplash&quot; /&gt;&lt;/p&gt;
&lt;h5 id=&quot;photo-by-samuel-regan-asante-on-unsplash&quot;&gt;Photo by &lt;a href=&quot;https://unsplash.com/@reganography?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Samuel Regan-Asante&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/a-sign-on-the-side-of-a-building-that-says-growing-concerns-STDn0DxY8os?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/h5&gt;

&lt;h2 id=&quot;github-copilot-cli-plugin-marketplace&quot;&gt;GitHub Copilot CLI plugin marketplace&lt;/h2&gt;

&lt;p&gt;The &lt;a href=&quot;https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/plugins-marketplace&quot;&gt;Copilot CLI&lt;/a&gt; lets you register a marketplace of plugins and install plugins from it. The on-ramp is one command:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;copilot plugin marketplace add OWNER/REPO
copilot plugin &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;some-plugin@some-marketplace
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A marketplace is just a GitHub repository with a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;marketplace.json&lt;/code&gt; file in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.github/plugin/&lt;/code&gt;. There is no review, no signing, no central index. Two marketplaces (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;copilot-plugins&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;awesome-copilot&lt;/code&gt;) are registered by default, but any user can add any other repo, including a personal fork or a newly created account that copies a real plugin name with a small change.&lt;/p&gt;

&lt;p&gt;Versioning is the next gap. The &lt;a href=&quot;https://docs.github.com/en/copilot/reference/cli-plugin-reference&quot;&gt;CLI plugin reference&lt;/a&gt; has a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;version&lt;/code&gt; field in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;plugin.json&lt;/code&gt;, but the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;copilot plugin install&lt;/code&gt; command has no syntax to pin to a version. You install &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OWNER/REPO&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OWNER/REPO:PATH&lt;/code&gt;, a Git URL, a local path, or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;plugin@marketplace&lt;/code&gt;, and you get whatever HEAD of the source happens to be at that moment. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;copilot plugin update NAME&lt;/code&gt; pulls latest. There is no lockfile, no SHA pinning of the kind &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gh skill&lt;/code&gt; has, and no provenance attestation. A marketplace can change a plugin’s contents without changing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;version&lt;/code&gt; field, and the next &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;update&lt;/code&gt; ships those changes to every developer who installed it.&lt;/p&gt;

&lt;p&gt;Plugins themselves are executable assets.They sit in the directory the marketplace points at, get pulled to the user’s machine, and run in the user’s shell context with whatever permissions the developer has. That is the same context as their git credentials, their cloud CLI sessions, and any local secrets in their environment.&lt;/p&gt;

&lt;p&gt;What is missing for an enterprise:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;No setting on a GitHub Enterprise or organization to restrict which marketplaces a Copilot CLI user is allowed to add. The MCP private registry policy that exists for VS Code does not cover this. I’d want at least to restrict this to repos under the organizations control.&lt;/li&gt;
  &lt;li&gt;No way to require signed plugins, or plugins from a verified publisher.&lt;/li&gt;
  &lt;li&gt;No audit trail on the GitHub side that tells you which plugins your developers installed and from where.&lt;/li&gt;
  &lt;li&gt;Clear versioning out of the box. preferably with provenance signing build in.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you compare this to how npm or PyPI are usually handled in a regulated org (a private proxy, an allowlist, a vulnerability scanner in the pipeline), the Copilot CLI plugin story today is roughly where npm was around 2014.&lt;/p&gt;

&lt;h2 id=&quot;github-copilot-cli-local-extensions&quot;&gt;GitHub Copilot CLI local extensions&lt;/h2&gt;

&lt;p&gt;Beyond the plugin marketplace there is a second, almost entirely undocumented extension surface baked into the Copilot CLI: the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.github/extensions/&lt;/code&gt; directory. &lt;a href=&quot;https://htek.dev/articles/github-copilot-cli-extensions-complete-guide/&quot;&gt;A detailed reverse-engineering writeup by htek.dev&lt;/a&gt; extracted this from the Copilot SDK source, because there is essentially no public documentation for it. The architecture is that the CLI discovers any subdirectory containing an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;extension.mjs&lt;/code&gt; file, forks it as a child Node.js process, and communicates with it over JSON-RPC via stdio. The extension calls &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;joinSession()&lt;/code&gt; and gets back a live session object that lets it register custom tools, intercept every agent lifecycle event, rewrite prompts, and make permission decisions.&lt;/p&gt;

&lt;p&gt;The lifecycle hooks are the part that matters from a governance angle:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onSessionStart&lt;/code&gt; — inject context into every session before the user’s first message&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onUserPromptSubmitted&lt;/code&gt; — rewrite or augment the user’s prompt before the agent sees it&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onPreToolUse&lt;/code&gt; — approve, deny, or modify the arguments of any tool call before it executes&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onPostToolUse&lt;/code&gt; — react after any tool completes, inject feedback the agent acts on&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onErrorOccurred&lt;/code&gt; — decide whether to retry, skip, or abort on failure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onPermissionRequest&lt;/code&gt; handler in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;joinSession()&lt;/code&gt; call replaces the standard user confirmation prompt for every tool execution. The SDK ships an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;approveAll&lt;/code&gt; import that does exactly what it sounds like: pass it and the extension silently approves every shell command, file write, and network call without showing the user a prompt.&lt;/p&gt;

&lt;p&gt;The discovery paths are what make this a fleet-wide concern:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Project-scoped&lt;/strong&gt;: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.github/extensions/&lt;/code&gt; is committed to the repo. Cloning the repo means the extensions are live the next time any developer opens a CLI session in that directory. No &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;install&lt;/code&gt; step. No opt-in. The moment the CLI opens the project, it forks whatever &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.mjs&lt;/code&gt; files are in that folder.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;User-scoped&lt;/strong&gt;: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.copilot/extensions/&lt;/code&gt; applies to every repo on the developer’s machine. An extension installed once here runs against every project that developer ever opens in the Copilot CLI, with no per-project awareness or consent.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Project extensions shadow user extensions on name collision, and the load order across extensions is not guaranteed — combined with a &lt;a href=&quot;https://github.com/github/copilot-cli/issues/2076&quot;&gt;known bug&lt;/a&gt; where multiple extensions registering hooks results in only the last-loaded extension’s hooks actually firing, this creates silent behavior that is hard to audit even locally.&lt;/p&gt;

&lt;p&gt;What is missing for an enterprise:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;No org-level allowlist. Any &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.github/extensions/&lt;/code&gt; directory in any repo a developer clones will have its extensions activated with no central gate.&lt;/li&gt;
  &lt;li&gt;No signing or publisher verification. An extension is an arbitrary &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.mjs&lt;/code&gt; file on disk.&lt;/li&gt;
  &lt;li&gt;No audit trail. The CLI does not log which extensions ran in a session or what hooks they fired.&lt;/li&gt;
  &lt;li&gt;No policy to restrict which &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onPermissionRequest&lt;/code&gt; handlers can suppress user prompts. A malicious or compromised extension can run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;approveAll&lt;/code&gt; and the developer sees nothing different.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The feature is legitimately useful: teams can enforce architecture rules, block destructive commands, run linters after edits, and build self-healing test loops. But those same capabilities — prompt rewriting, permission suppression, tool argument modification — are exactly what a supply-chain attack would want, and right now there is no org-level control surface at all.&lt;/p&gt;

&lt;h2 id=&quot;agent-package-manager-apm&quot;&gt;Agent Package Manager (APM)&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/microsoft/apm&quot;&gt;Microsoft APM&lt;/a&gt; is a dependency manager for AI agent context. You declare an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apm.yml&lt;/code&gt;, run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apm install&lt;/code&gt;, and it pulls instructions, skills, prompts, agents, hooks, plugins, and MCP servers from any git host (GitHub, GitLab, Bitbucket, Azure DevOps, GitHub Enterprise) into every detected agent client on the machine.&lt;/p&gt;

&lt;p&gt;The manifest lives in the repo itself (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apm.yml&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apm.lock.yaml&lt;/code&gt; are committed alongside the code, like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;package.json&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;package-lock.json&lt;/code&gt;). It looks like this:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;dependencies&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;apm&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;anthropics/skills/skills/frontend-design&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;github/awesome-copilot/plugins/context-engineering&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;microsoft/apm-sample-package#v1.0.0&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;mcp&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;io.github.github/github-mcp-server&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;transport&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;http&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;APM does ship a governance story, and it is the most thought-through one in this post. There is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apm-policy.yml&lt;/code&gt; with tighten-only inheritance from enterprise to org to repo, a published bypass contract, hidden-Unicode scanning on every install, lockfile integrity hashes, and an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apm audit --ci&lt;/code&gt; mode that you can wire into branch protection.&lt;/p&gt;

&lt;p&gt;The catch is that all of this governance is opt-in and lives outside of GitHub itself. A fresh install of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apm&lt;/code&gt; on a developer laptop has no policy file. Policy is also pull-only: the canonical org policy lives at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;org&amp;gt;/.github/apm-policy.yml&lt;/code&gt; and the CLI fetches it on demand when a developer or CI job runs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apm install&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apm audit --ci&lt;/code&gt;. There is no push, no agent, and no central enrollment. The fetched policy is cached locally for an hour by default in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apm_modules/.policy-cache/&lt;/code&gt;, and a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetch_failure: warn|block&lt;/code&gt; knob decides what happens when the org repo is unreachable. The default is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;warn&lt;/code&gt;, which means an offline laptop with an empty cache resolves with no policy at all. Repo-local &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apm-policy.yml&lt;/code&gt; files can &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;extends: org&lt;/code&gt; and only &lt;strong&gt;tighten&lt;/strong&gt; the parent rules, never relax them. See the &lt;a href=&quot;https://microsoft.github.io/apm/enterprise/policy-reference/&quot;&gt;Policy Reference&lt;/a&gt; and &lt;a href=&quot;https://microsoft.github.io/apm/enterprise/governance-guide/&quot;&gt;Governance Guide&lt;/a&gt; for the full mechanics.&lt;/p&gt;

&lt;p&gt;Until your security team writes one, publishes it, and gets it picked up on every machine, an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apm install&lt;/code&gt; will happily resolve transitive dependencies from any reachable git host. And don’t worry: your CI/CD pipeline will do the same (if the tooling is already installed)!&lt;/p&gt;

&lt;p&gt;There is also no auto-install. APM is purely a CLI; it has no editor extension that runs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apm install&lt;/code&gt; when you open a repo in VS Code. The docs frame it explicitly as “same as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm install&lt;/code&gt; after cloning a Node project”, which means the install step relies on the developer running it (or a devcontainer &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;postCreateCommand&lt;/code&gt;, or a CI job). The flip side is that the &lt;strong&gt;deployed&lt;/strong&gt; files (under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.github/&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.claude/&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.cursor/&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.gemini/&lt;/code&gt;) are recommended to be committed, so a teammate who clones the repo gets the agent context immediately, before they ever run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apm install&lt;/code&gt;. That is convenient and it also means the agent is reading APM-deployed content the moment the editor opens the repo, regardless of whether the local CLI was ever invoked.&lt;/p&gt;

&lt;p&gt;APM packages can declare &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scripts&lt;/code&gt; (think npm scripts), and the policy reference exposes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;manifest.scripts: allow|deny&lt;/code&gt; precisely because of this risk. Default is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;allow&lt;/code&gt;. So an attacker who lands a package in your dependency tree can also land scripts, unless your org policy denies them outright.&lt;/p&gt;

&lt;p&gt;Versioning is fine on the manifest side: dependencies pin with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#tag&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#sha&lt;/code&gt;, the lockfile records resolved commit SHAs and content hashes, and the org policy can &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;require&lt;/code&gt; specific versions with a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;require_resolution&lt;/code&gt; of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;project-wins&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;policy-wins&lt;/code&gt;, or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;block&lt;/code&gt;. Updates happen on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apm install --update&lt;/code&gt;, not implicitly. Direct and transitive resolution stay the parts I would worry about: a package you trusted six months ago can pull in a new dependency on its next release, and unless your org policy has a tight &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dependencies.allow&lt;/code&gt; pattern, the new source slips through.&lt;/p&gt;

&lt;p&gt;The MCP integration is worth a separate paragraph. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apm install --mcp NAME&lt;/code&gt; adds an entry under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dependencies.mcp&lt;/code&gt; in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apm.yml&lt;/code&gt; and writes the resolved server config straight into the native config file of every detected client (Copilot, Claude, Cursor, Codex, OpenCode, Gemini) on the filesystem, bypassing each client’s own registry or policy layer. The full mechanism is documented in the &lt;a href=&quot;https://microsoft.github.io/apm/guides/mcp-servers/&quot;&gt;APM MCP Servers guide&lt;/a&gt;. Convenient for a developer; also a clean way around whatever per-client policy exists. You are then relying on the runtime side of those clients to apply policy, and only a few of them do, with workarounds.&lt;/p&gt;

&lt;h2 id=&quot;gh-skill-now-in-the-github-cli&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gh skill&lt;/code&gt; now in the GitHub CLI&lt;/h2&gt;
&lt;p&gt;Note: this is the &lt;strong&gt;GitHub&lt;/strong&gt; CLI, not the GitHub &lt;strong&gt;Copilot&lt;/strong&gt; CLI!&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.blog/changelog/2026-04-16-manage-agent-skills-with-github-cli/&quot;&gt;gh skill&lt;/a&gt; command lets you discover, install, manage, and publish &lt;a href=&quot;https://agentskills.io&quot;&gt;Agent Skills&lt;/a&gt; from any GitHub repository:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gh skill &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;github/awesome-copilot documentation-writer
gh skill &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;some-user/some-repo some-skill &lt;span class=&quot;nt&quot;&gt;--pin&lt;/span&gt; v1.0.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The supply-chain features here are better. Skills can be pinned to a tag (unsafe) or commit SHA, the install records the git tree SHA in the skill’s frontmatter, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gh skill update&lt;/code&gt; compares local SHAs against the remote, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gh skill publish&lt;/code&gt; will offer to enable &lt;a href=&quot;https://docs.github.com/repositories/releasing-projects-on-github/about-releases&quot;&gt;immutable releases&lt;/a&gt; so that even a repo admin cannot rewrite a published version.&lt;/p&gt;

&lt;p&gt;Audit tooling is thin. The &lt;a href=&quot;https://cli.github.com/manual/gh_skill&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gh skill&lt;/code&gt; manual&lt;/a&gt; lists &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;install&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;preview&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;publish&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;search&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;update&lt;/code&gt; as the only subcommands. There is a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gh skill preview&lt;/code&gt; to inspect a skill’s content before installing, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gh skill update&lt;/code&gt; uses the stored tree SHA to detect drift, but there is no &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gh skill audit&lt;/code&gt; and no org-side audit log of what your developers installed. If you want to know which skills landed on a developer’s laptop, you have to grep the agent host directories yourself.&lt;/p&gt;

&lt;p&gt;The thing that is not there is an org-level allowlist.GitHub itself is unusually direct about this in the changelog:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Skills are installed at your own discretion. They are not verified by GitHub and may contain prompt injections, hidden instructions, or malicious scripts. We strongly recommend inspecting the content of skills before installation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So the tooling around a single skill is solid, the tooling around “which skills is my org allowed to use” is not. A developer can &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gh skill install&lt;/code&gt; from any public repo, and the agent host (Copilot, Claude Code, Cursor, Codex, Gemini, all the CLI options) will pick the skill up the next time it scans the directory. Skills are a first-class extension point for the agent’s behavior, which means a malicious skill is closer to a custom system prompt than to a passive config file.&lt;/p&gt;

&lt;p&gt;Dependabot does not help here either: agent skills, MCP servers, APM packages, and Copilot CLI plugins are not on the &lt;a href=&quot;https://docs.github.com/en/code-security/dependabot/ecosystems-supported-by-dependabot/supported-ecosystems-and-repositories&quot;&gt;list of ecosystems Dependabot supports&lt;/a&gt;. That means no automatic update PRs, no security advisories wired in, and no scheduled drift detection across these surfaces. You would have to build that yourself.&lt;/p&gt;

&lt;h2 id=&quot;mcp-servers-across-editors&quot;&gt;MCP servers across editors&lt;/h2&gt;

&lt;p&gt;This is the area where the situation has gotten more confusing rather than less, even though there has been real work on it. Back in March 2025 MCP exploded into the AI world: extensibility from anywhere into anything! Since then, a lot of servers and OSS repos turned out to be playing around with things. The hard part is that a large share of those repos have since been abandoned. Endor Labs covered this in its &lt;a href=&quot;https://www.endorlabs.com/learn/state-of-dependency-management-2025&quot;&gt;State of Dependency Management 2025 report&lt;/a&gt; (summary on the &lt;a href=&quot;https://www.prnewswire.com/news-releases/endor-labs-launches-2025-state-of-dependency-management-report-finds-80-of-ai-suggested-dependencies-contain-risks-302603438.html&quot;&gt;Endor Labs press release&lt;/a&gt;): more than 10,000 MCP servers were created in less than a year, 75% of them by individual developers rather than organizations, around 40% have no license at all, and 82% touch sensitive APIs. Maintenance signals on the long tail are weak, which means the same servers your developers happily installed last year may already be effectively orphaned.&lt;/p&gt;

&lt;p&gt;A short summary of where MCP server config lives today:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;VS Code: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.vscode/mcp.json&lt;/code&gt; (workspace), the user-profile &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mcp.json&lt;/code&gt; opened via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MCP: Open User Configuration&lt;/code&gt;, or contributed by an installed VS Code extension. The full schema is in the &lt;a href=&quot;https://code.visualstudio.com/docs/copilot/customization/mcp-servers&quot;&gt;VS Code MCP servers docs&lt;/a&gt;. APM sits on top of this: it stores MCP servers in the repo’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apm.yml&lt;/code&gt;, then writes them into the same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.vscode/mcp.json&lt;/code&gt; file the editor reads. By default &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apm install&lt;/code&gt; does not overwrite locally-authored entries (that needs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--force&lt;/code&gt;, per the &lt;a href=&quot;https://microsoft.github.io/apm/reference/cli-commands/&quot;&gt;CLI reference&lt;/a&gt;), so the file you end up with is APM’s set &lt;strong&gt;plus&lt;/strong&gt; anything that was already there. If a developer thinks “I’m only running the APM-managed servers”, they are wrong: they are running APM-managed plus whatever they (or another tool) wrote into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mcp.json&lt;/code&gt; previously.&lt;/li&gt;
  &lt;li&gt;Cursor, Windsurf, Codex, Claude Code, Gemini CLI, Copilot and other CLI’s: each has its own file in its own location, with its own schema variations.&lt;/li&gt;
  &lt;li&gt;The remote Copilot agents (Cloud Agent, Spark, Spaces, Review Agent) each have their own configuration surface and can only be configured by a repo admin.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GitHub did ship an &lt;a href=&quot;https://docs.github.com/en/copilot/how-tos/administer-copilot/manage-mcp-usage/configure-mcp-registry&quot;&gt;MCP private registry policy&lt;/a&gt; for Copilot that lets an enterprise restrict which MCP servers Copilot users can connect to. Useful, and a real step forward, but at the time of writing it only applies inside Copilot in VS Code. The same Copilot identity used in JetBrains, Neovim, the CLI, Spark, Spaces, the Cloud Agent, or the Review Agent is not covered by that policy.&lt;/p&gt;

&lt;p&gt;Two patterns make the policy easier to bypass than it looks:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Local stdio servers. The &lt;a href=&quot;https://code.visualstudio.com/docs/copilot/customization/mcp-servers&quot;&gt;VS Code MCP docs&lt;/a&gt; describe three config paths: the gallery flow (which the Copilot private registry can gate), the workspace &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.vscode/mcp.json&lt;/code&gt;, and the user-profile &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mcp.json&lt;/code&gt;. The registry policy applies to the gallery flow. A developer who edits either JSON file directly gets a one-time “trust this server” prompt and the server starts. There is a separate VS Code device-management policy that can disable MCP entirely, but it is on/off, not allowlist-aware. See &lt;a href=&quot;https://code.visualstudio.com/docs/configure/extensions/extension-runtime-security&quot;&gt;extension runtime security&lt;/a&gt; for the surrounding policy surface.&lt;/li&gt;
  &lt;li&gt;Extension-contributed servers. A VS Code extension can contribute MCP servers through its manifest. If an extension is allowed to install (and most orgs do not gate extensions tightly, see the next section), the MCP servers it contributes inherit the same trust as the extension itself. That sidesteps the registry policy entirely.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Even worse: clone the extension repo from github.com, build it, and just use the compiled VSIX file in VS Code!&lt;/p&gt;

&lt;p&gt;So the practical state is: you can get a meaningful slice of governance for Copilot in VS Code if you set up the registry, and almost no governance for any of the other clients on the same laptop, all of which can reach the same internal systems. So we are not there yet, but at least a step in the right direction.&lt;/p&gt;

&lt;h2 id=&quot;vs-code-extensions-and-the-registry-split&quot;&gt;VS Code extensions and the registry split&lt;/h2&gt;

&lt;p&gt;The extension story is the oldest of the five, and it is the one that has changed shape most recently because of the Cursor and Windsurf-style forks. A few things to be explicit about:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The Microsoft Visual Studio Marketplace is closed to non-Microsoft products by its terms of use. Any VS Code fork (Cursor, Windsurf, VSCodium, Kiro, Antigravity, Positron) cannot legally use it.&lt;/li&gt;
  &lt;li&gt;Those forks generally point at &lt;a href=&quot;https://open-vsx.org/&quot;&gt;Open VSX&lt;/a&gt;, the Eclipse Foundation registry. Open VSX has a smaller catalog, less aggressive abuse handling historically, and a publish flow that is easier to ride.&lt;/li&gt;
  &lt;li&gt;On April 21, 2026 the Eclipse Foundation &lt;a href=&quot;https://newsroom.eclipse.org/news/announcements/eclipse-foundation-launches-open-vsx-managed-registry-0&quot;&gt;launched the Open VSX Managed Registry&lt;/a&gt; as an SLA-backed paid tier (99.95% uptime, defined support tiers), with AWS, Google, and Cursor as initial adopters. The launch numbers paint the scale: 300M+ downloads per month, 200M+ daily requests at peak, 12,000+ extensions, 8,000+ publishers. The community instance was being asked to do the job of always-on critical infrastructure, and the AI editors are most of the reason.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For an org this means the threat model differs by editor, even when the developer thinks they are installing “the same extension”. A name on the Microsoft Marketplace is not necessarily owned by the same publisher on Open VSX. Typosquats and copy-jobs of popular extensions show up regularly on both registries, and an extension is essentially arbitrary code in your editor process with access to your workspace files, your environment, and any tokens the editor holds.&lt;/p&gt;

&lt;p&gt;The MCP angle ties back in here: an extension can contribute MCP servers, settings, and language model providers. So an extension that gets past your install policy can reintroduce all the things you tried to gate at the registry layer.&lt;/p&gt;

&lt;p&gt;What helps in practice:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The VS Code &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;extensions.allowed&lt;/code&gt; and related policies, deployed through your endpoint management, so that only an allowlist of extensions can install at all.&lt;/li&gt;
  &lt;li&gt;Mirroring Open VSX internally if you support fork editors, with a curated subset rather than a full passthrough.&lt;/li&gt;
  &lt;li&gt;Treating new extension installs the same way you treat new npm dependencies: review, scan, and budget for the maintenance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Endpoint protection is the layer that catches what the registries miss. Even the official VS Code documentation on &lt;a href=&quot;https://code.visualstudio.com/docs/configure/extensions/extension-runtime-security&quot;&gt;extension runtime security&lt;/a&gt; is direct that an extension runs with the user’s full permissions: it can read and write any file the editor can, spawn processes, and make network calls. The Marketplace does scan packages and verify signatures (see the Microsoft post on &lt;a href=&quot;https://developer.microsoft.com/blog/security-and-trust-in-visual-studio-marketplace&quot;&gt;security and trust in the Visual Studio Marketplace&lt;/a&gt;), but malicious extensions and credential-stealing supply chain incidents keep landing (see the &lt;a href=&quot;https://www.wiz.io/blog/supply-chain-risk-in-vscode-extension-marketplaces&quot;&gt;Wiz writeup on supply chain risk in VS Code extension marketplaces&lt;/a&gt; and Check Point’s &lt;a href=&quot;https://blog.checkpoint.com/securing-the-cloud/malicious-vscode-extensions-with-more-than-45k-downloads-steal-pii-and-enable-backdoors/&quot;&gt;report on 45,000+ downloads of malicious extensions&lt;/a&gt;). For an org that means the controls have to live below the editor: managed device policy that blocks unsigned binaries, EDR that watches the editor’s process tree the same way it watches a browser, outbound DNS and TLS inspection that can flag the unusual call patterns an extension makes, and a workstation lifecycle that assumes a compromised editor is one of the realistic incidents you respond to. Third-party scanners like &lt;a href=&quot;https://extensiontotal.com&quot;&gt;ExtensionTotal&lt;/a&gt; can give you a per-extension risk score before you allow it, but treat them as an addition to your endpoint stack rather than a substitute.&lt;/p&gt;

&lt;h2 id=&quot;new-vs-code-enterprise-policy-updates&quot;&gt;New: VS Code enterprise policy updates&lt;/h2&gt;

&lt;p&gt;VS Code shipped a notable batch of new enterprise policies around late April 2026 (documented at &lt;a href=&quot;https://code.visualstudio.com/docs/enterprise/policies&quot;&gt;code.visualstudio.com/docs/enterprise/policies&lt;/a&gt;) that start closing some of the gaps described above. The most relevant additions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP server control&lt;/strong&gt; — &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ChatMCP&lt;/code&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chat.mcp.access&lt;/code&gt;) lets an admin disable access to all installed MCP servers via device policy. This is a blunter but more reliable control than the Copilot private registry alone, because it applies regardless of how the server was registered.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Network filtering for agent tools&lt;/strong&gt; — &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ChatAgentNetworkFilter&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ChatAgentAllowedNetworkDomains&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ChatAgentDeniedNetworkDomains&lt;/code&gt; let you restrict which hosts an agent’s fetch tool and integrated browser can reach. Combined with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ChatAgentSandboxEnabled&lt;/code&gt;, which runs terminal commands in a sandboxed environment, this starts to limit the blast radius of a compromised or malicious tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent tool approval&lt;/strong&gt; — &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ChatToolsAutoApprove&lt;/code&gt; lets you lock down the “YOLO mode” (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chat.tools.global.autoApprove&lt;/code&gt;) at the org level so individual developers cannot enable it, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ChatToolsEligibleForAutoApproval&lt;/code&gt; lets you force specific tools to always require manual confirmation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Account-gated AI access&lt;/strong&gt; — &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ChatApprovedAccountOrganizations&lt;/code&gt; blocks all AI features until the user signs into a GitHub account belonging to an approved organization. Useful for contractors, BYOD, and split environments where you need to tie the Copilot seat to an identity your org controls before anything runs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Linux policy support&lt;/strong&gt; — VS Code 1.106 added a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/vscode/policy.json&lt;/code&gt; file for Linux devices, meaning the same policies you deploy on Windows and macOS via ADMX or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.mobileconfig&lt;/code&gt; can now cover Linux developer workstations through your existing config management tooling (Ansible, Puppet, Chef, Salt).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Policy diagnostics&lt;/strong&gt; — A new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Developer: Policy Diagnostics&lt;/code&gt; command generates a Markdown report of which policies are active, what values are in effect, and whether the Account Policy Gate is satisfied or blocked. Useful when you need to prove to an auditor — or a confused developer — exactly what the machine is enforcing.&lt;/p&gt;

&lt;p&gt;These additions meaningfully strengthen the VS Code row in the summary table below. The gaps at the Copilot CLI, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gh skill&lt;/code&gt;, and cross-editor MCP layers remain open.&lt;/p&gt;

&lt;h2 id=&quot;state-of-the-plugin-governance-for-github-copilot&quot;&gt;State of the plugin governance for GitHub Copilot&lt;/h2&gt;

&lt;p&gt;If I line up the different surfaces by how much org-level governance is actually possible today:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Surface&lt;/th&gt;
      &lt;th&gt;Org-level allowlist&lt;/th&gt;
      &lt;th&gt;Provenance / pinning&lt;/th&gt;
      &lt;th&gt;Notes&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Copilot CLI plugin marketplace&lt;/td&gt;
      &lt;td&gt;None&lt;/td&gt;
      &lt;td&gt;None&lt;/td&gt;
      &lt;td&gt;Any GitHub repo can be a marketplace&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Copilot CLI local extensions&lt;/td&gt;
      &lt;td&gt;None&lt;/td&gt;
      &lt;td&gt;None&lt;/td&gt;
      &lt;td&gt;Committed to repo; active on clone with no install step&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;APM&lt;/td&gt;
      &lt;td&gt;Yes, via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apm-policy.yml&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Lockfile + content hashes&lt;/td&gt;
      &lt;td&gt;Policy is opt-in, customer-owned&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gh skill&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;None&lt;/td&gt;
      &lt;td&gt;Tag and SHA pinning&lt;/td&gt;
      &lt;td&gt;GitHub explicitly mentions verification&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;MCP servers&lt;/td&gt;
      &lt;td&gt;Limited (Copilot in VS Code only)&lt;/td&gt;
      &lt;td&gt;None standardized&lt;/td&gt;
      &lt;td&gt;Local stdio and extension-contributed servers bypass the policy&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;VS Code extensions&lt;/td&gt;
      &lt;td&gt;Yes, via VS Code policy&lt;/td&gt;
      &lt;td&gt;Marketplace + signature&lt;/td&gt;
      &lt;td&gt;Differs across forks and Open VSX&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The pattern across all of them is that the per-developer experience is great, the per-org enforcement is either absent or has to be assembled from policies that live in different places than the feature itself. None of these are unfixable, and APM in particular shows what the right shape looks like, but the gap between “shipped” and “safe to deploy at scale” is wider than the changelog posts suggest.&lt;/p&gt;

&lt;p&gt;If you are responsible for any of this in a larger org, the short version of what I would do:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Decide which of these surfaces you want your developers to use at all. Default-allow is a choice that has consequences, not a neutral starting point.&lt;/li&gt;
  &lt;li&gt;For the ones you allow, pick the strongest available control today (VS Code extension policy, the Copilot MCP registry, an APM policy file) and ship it.&lt;/li&gt;
  &lt;li&gt;For the ones with no control today (CLI plugins, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gh skill&lt;/code&gt;), at minimum log and review, and feed back to GitHub and Microsoft that this gap matters.
Overall, tighten your grip on endpoint protection and your firewall/proxy configurations.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The features themselves are fine. The missing layer is the one every package ecosystem has had to grow eventually: a place for an org to say which sources it trusts, applied uniformly across every client that can pull from them.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Running GitHub Copilot CLI on local AI inference</title>
			<link href="https://devopsjournal.io/blog/2026/04/12/Running-GitHub-Copilot-CLI-on-local-AI"/>
			<updated>2026-04-12T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2026/04/12/Running-GitHub-Copilot-CLI-on-local-AI</id>
			<content type="html">&lt;p&gt;I’ve been using the GitHub Copilot CLI as my main terminal assistant for a while now. It works great with GitHub-hosted models (Claude, GPT-4) but that means every command, every file you give it context about, every prompt goes over the wire to a cloud provider. And what’s worse: it means that the cloud provider is hosting the beefy LLM model for me, incurring a lot of compute cost. So I wanted to explore what it looks like to run the whole thing locally — both for privacy and just to see how far local models have come. I think I have a good setup on a recent laptop, with two GPU’s and an NPU, so let’s see how far we can go.&lt;/p&gt;

&lt;p&gt;The Copilot CLI now supports a “bring your own key” (BYOK) mode where you point it at any OpenAI-compatible API endpoint. That means any local inference engine that exposes a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/v1/chat/completions&lt;/code&gt; endpoint should work in theory. In practice there’s quite a bit to figure out.&lt;/p&gt;

&lt;h2 id=&quot;hardware-i-was-testing-on&quot;&gt;Hardware I was testing on&lt;/h2&gt;

&lt;p&gt;My machine is a Dell Pro Max 14 MC14250 from Q4, 2025. It has an Intel Core Ultra 7 265H processor in it, with 32GB RAM and a 1TB SSD. The interesting thing about this machine is that it has three separate AI-capable processors:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Intel Arc 140T&lt;/strong&gt; (GPU 0) — 16 GB VRAM, integrated but a full GPU with dedicated AI acceleration&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;NVIDIA RTX PRO 500 Blackwell&lt;/strong&gt; (GPU 1) — 6.1 GB dedicated VRAM (GDDR — fast video memory separate from system RAM)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Intel AI Boost NPU&lt;/strong&gt; — dedicated neural processing unit built into the CPU&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On paper that’s a lot of local AI horsepower. In practice, as I found out, things are more complicated.&lt;/p&gt;

&lt;h2 id=&quot;how-the-byok-configuration-works&quot;&gt;How the BYOK configuration works&lt;/h2&gt;

&lt;p&gt;The Copilot CLI reads four environment variables to switch from cloud to local inference. If you have a local ollama instance running, you can point the CLI at it with:&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;COPILOT_PROVIDER_BASE_URL&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;          &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;http://localhost:11434/v1&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;COPILOT_MODEL&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;                      &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;qwen2.5:7b-instruct-32k&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;COPILOT_PROVIDER_MAX_PROMPT_TOKENS&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;32768&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;COPILOT_PROVIDER_MAX_OUTPUT_TOKENS&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;8192&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Presuming you have downloaded that model into Ollama and it’s running on that port of course. The CLI will then send all requests to that local endpoint instead of the cloud.&lt;/p&gt;

&lt;p&gt;You don’t need &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;COPILOT_PROVIDER_TYPE&lt;/code&gt; (it defaults to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;openai&lt;/code&gt;, which is compatible with all the local engines below) and you don’t need an API key for local inference. To switch back to cloud, you just remove those variables.&lt;/p&gt;

&lt;p&gt;One critical thing I learned early: the &lt;strong&gt;context window matters a lot&lt;/strong&gt;. The Copilot CLI system prompt including all tool definitions is around &lt;strong&gt;21,000 tokens&lt;/strong&gt;. That’s before you’ve typed a single character. Any model running with less than 32k context will get its prompt truncated and start behaving strangely — either losing tools mid-conversation or giving incoherent responses.&lt;/p&gt;

&lt;h2 id=&quot;what-i-tried&quot;&gt;What I tried&lt;/h2&gt;

&lt;h3 id=&quot;ollama--works-with-some-gotchas&quot;&gt;Ollama — works, with some gotchas&lt;/h3&gt;

&lt;p&gt;Ollama is the easiest starting point. Install it, pull a model, and it’s running an OpenAI-compatible API on port 11434. The first thing I ran into was the URL: you need the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/v1&lt;/code&gt; suffix (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost:11434/v1&lt;/code&gt;), not just &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost:11434&lt;/code&gt;. The docs example omits it and you get a 404.&lt;/p&gt;

&lt;p&gt;The second thing I ran into was the context window. By default most Ollama models run with a 2048 or 4096 context window. With a 21k system prompt you need at least 32k. I had to create a custom &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Modelfile&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;FROM qwen2.5:7b-instruct
PARAMETER num_ctx 32768
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then create it with: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ollama create qwen2.5:7b-instruct-32k -f Modelfile&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;A couple of performance settings also helped, particularly for flash attention and KV cache quantization. The KV cache (key-value cache) stores the intermediate attention values the model computes for each token — with a 32k context window it can consume 1–2 GB of VRAM, so compressing it matters:&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;System.Environment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SetEnvironmentVariable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;OLLAMA_FLASH_ATTENTION&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;User&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;System.Environment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SetEnvironmentVariable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;OLLAMA_KV_CACHE_TYPE&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;q8_0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;User&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Restart Ollama after setting these. The NVIDIA RTX PRO 500 can handle the 32k KV cache with these settings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On model selection:&lt;/strong&gt; not all models support tool calling, which the Copilot CLI relies on heavily. From what I tested, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gemma3&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mistral:7b&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qwen2.5-coder:1.5b-base&lt;/code&gt; all fail because they don’t support tool calls. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qwen2.5:7b-instruct&lt;/code&gt; works well. Llama 3.1 8B also works but quality is mediocre.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ollama verdict:&lt;/strong&gt; ✅ Works. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qwen2.5:7b-instruct-32k&lt;/code&gt; on the NVIDIA GPU is the sweet spot. Slow (~30% GPU utilisation, around 13 tokens/sec) but functional.&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;foundry-local--almost-entirely-blocked-because-of-driver-limitations&quot;&gt;Foundry Local — almost entirely blocked because of driver limitations&lt;/h3&gt;

&lt;p&gt;Microsoft Foundry Local is interesting because it uses OpenVINO and can target all three AI processors: the Arc GPU, the NPU, and CUDA. I installed it with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;winget install Microsoft.FoundryLocal&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;One nice thing about Foundry Local is that it automatically picks the best model variant for your hardware. You just say &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;foundry model run qwen2.5-7b&lt;/code&gt; and it figures out whether to download the CUDA, OpenVINO GPU, or NPU variant.&lt;/p&gt;

&lt;p&gt;That sounds great, but I hit walls on every path:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NPU (Intel AI Boost):&lt;/strong&gt; All the NPU-optimised models cap at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;maxInputTokens=3696&lt;/code&gt;. The CLI needs ~21k. That’s a hard fail — not a configuration issue, it’s a hardware architectural limit of how much context the NPU can process in one inference pass. Nothing to be done here until Intel expands the NPU context window in future driver versions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenVINO GPU (Arc 140T):&lt;/strong&gt; This is the interesting path — the Arc 140T has 16GB and could run 7B models comfortably. But every OpenVINO GPU model I tried threw &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EPContext node not found&lt;/code&gt;. The root cause seems to be a driver version mismatch: the Arc driver I have installed (32.0.101.8509, the Dell OEM version from November 2025) is older than what Foundry Local’s OpenVINO runtime needs (32.0.101.8629, released April 2026). Dell hasn’t published the new driver for the MC14250 yet. Once that driver lands, this path should work well — 7B models on 16GB with no memory pressure. I’ve had some driver issues in the past, so moving over to the raw driver from Intel’s site is a bit scary for now, but it’s on the table if Dell doesn’t update soon.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CUDA (NVIDIA):&lt;/strong&gt; 7B models crash with Out of Memory (OOM). 6GB VRAM isn’t enough for the model weights plus the KV cache for 28k context. A 1.5B model works but it’s not capable enough for agentic tasks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Foundry Local verdict:&lt;/strong&gt; ❌ Blocked by driver version. Worth revisiting once Dell publishes the Arc driver update.&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;lm-studio--works-with-the-right-configuration&quot;&gt;LM Studio — works with the right configuration&lt;/h3&gt;

&lt;p&gt;LM Studio uses llama.cpp under the hood and exposes an OpenAI-compatible server. It supports Vulkan, which means it can run on Intel Arc without needing OpenVINO or driver-specific binaries.&lt;/p&gt;

&lt;p&gt;Install it with:&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;winget&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;install&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ElementLabs.LMStudio&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--scope&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then there are several things to get right:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context window:&lt;/strong&gt; Defaults to 4096. The model load fails with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n_keep &amp;gt;= n_ctx&lt;/code&gt; until you change it. In the Developer tab, set the context length to &lt;strong&gt;32768&lt;/strong&gt; before loading the model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;URL:&lt;/strong&gt; Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://127.0.0.1:1234/v1&lt;/code&gt;, not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost:1234/v1&lt;/code&gt;. LM Studio binds IPv4 only and on some Windows machines &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;localhost&lt;/code&gt; resolves to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;::1&lt;/code&gt; (IPv6), causing a connection refused.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GPU selection:&lt;/strong&gt; This was the most complex part. LM Studio’s default is the CUDA backend when a CUDA-capable GPU is present — meaning it runs on the NVIDIA card, not Arc. Getting it onto Arc requires switching the backend to Vulkan, which means editing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%USERPROFILE%\.lmstudio\.internal\backend-preferences-v1.json&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;model_format&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;gguf&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;llama.cpp-win-x86_64-vulkan-avx2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2.13.0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But even with Vulkan enabled, LM Studio still defaulted to the NVIDIA card — because under Vulkan, the NVIDIA card (Vulkan device 1) is classified as “Discrete” and gets priority over the Arc (Vulkan device 0, classified as “Integrated”). The fix is to go to &lt;strong&gt;Settings → Hardware&lt;/strong&gt; in LM Studio and disable the RTX PRO 500. LM Studio then writes this to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hardware-config.json&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:[[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;llama.cpp-win-x86_64-vulkan-avx2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;fields&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:[{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;key&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;load.gpuSplitConfig&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;strategy&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;evenly&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;disabledGpus&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;priority&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:[],&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;customRatio&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:[]}}]}]],&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;meta&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;values&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;map&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]}}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Arc 140T alone is too slow.&lt;/strong&gt; This surprised me. The Arc has 16GB but it’s &lt;em&gt;integrated&lt;/em&gt; memory — shared system memory (LPDDR5x) at ~68 GB/s bandwidth. Running a 7B model on it saturated the memory bus and froze my entire machine, including the mouse. LLM inference is completely memory-bandwidth-bound, and 68 GB/s shared with the CPU simply isn’t enough for interactive use.&lt;/p&gt;

&lt;p&gt;The working configuration ended up being to offload &lt;strong&gt;25 layers of the model to NVIDIA GDDR, the rest on CPU&lt;/strong&gt;. LM Studio auto-selects this split when both GPUs are enabled. The NVIDIA’s dedicated GDDR bandwidth handles the hot layers and the CPU handles the rest. Inference takes longer than pure NVIDIA CUDA, but it works without freezing the machine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model selection for LM Studio:&lt;/strong&gt; My first download was &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qwen2.5.1-coder-7b-instruct&lt;/code&gt;, which technically works but is mediocre at agentic tasks — it doesn’t proactively explore the repository you’re working in and needs very explicit prompting. The coder fine-tune trades general instruction following for code completion, which hurts agentic behaviour.&lt;/p&gt;

&lt;p&gt;Switching to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qwen2.5-7b-instruct@q5_k_m&lt;/code&gt; (the bartowski build from Discover) gave better results. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@q5_k_m&lt;/code&gt; suffix is the quantization level — 5-bit compressed weights, a smaller and faster-to-run version of the model with only a marginal quality trade-off versus the full-precision original. Still not at the quality level of online Claude, but the tool calling works correctly and it handles the CLI’s complex nested JSON schemas for things like asking the user clarifying questions.&lt;/p&gt;

&lt;p&gt;I also tried &lt;strong&gt;Gemma 4 E4B&lt;/strong&gt;, which is Google’s newest model with 128K context and native tool calling support. The context window is great (it easily fits the CLI prompt with room to spare) but it consistently generated malformed JSON schemas when calling the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ask_user&lt;/code&gt; tool — making up property names that don’t exist in the schema. Small models struggle with deeply nested JSON Schema definitions and Gemma 4 E4B isn’t an exception.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lms load&lt;/code&gt; re-load trap.&lt;/strong&gt; If you call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lms load&lt;/code&gt; while a model is already running, LM Studio doesn’t replace it — it creates a second instance with a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:2&lt;/code&gt; suffix (e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qwen2.5-7b-instruct@q4_k_s:2&lt;/code&gt;). The original stays loaded with its old context window. Since the Copilot CLI env var points to the plain identifier, it hits the old instance. Always unload first to free up GPU memory and avoid confusion:&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;lms&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;unload&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--all&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lms&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;qwen2.5-7b-instruct&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;q4_k_s&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--gpu&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;0.78&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--context-length&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;32768&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--gpu&lt;/code&gt; trap with the CLI.&lt;/strong&gt; After all the above I noticed that loading via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lms load qwen2.5-7b-instruct@q4_k_s --gpu max&lt;/code&gt; sometimes silently fell back to CPU. The only sign is GPU memory: 1852 MiB = CPU mode; 4914 MiB = GPU split. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--gpu&lt;/code&gt; flag takes a &lt;strong&gt;0–1 fraction of layers&lt;/strong&gt;, not a layer count or “max”. Using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--gpu 0.78&lt;/code&gt; reliably puts ~25/32 layers on the NVIDIA GDDR which is the max layer config that fits on my GPU. Full load command:&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;lms&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;qwen2.5-7b-instruct&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;q4_k_s&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--gpu&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;0.78&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--context-length&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;32768&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Also worth noting: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@q4_k_s&lt;/code&gt; (4-bit quantization, 4.46 GB) is slightly faster than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@q5_k_m&lt;/code&gt; (5-bit quantization, 4.78 GB) because the smaller file leaves more GPU headroom for the KV cache. In practice the quality difference between Q4_K_S and Q5_K_M is imperceptible for agentic tasks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LM Studio verdict:&lt;/strong&gt; ✅ Works with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qwen2.5-7b-instruct@q4_k_s --gpu 0.78&lt;/code&gt;, 25 layers on NVIDIA, rest on CPU. Fastest 7B config measured on this machine. NPU is not accessible from LM Studio at all.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;what-the-npu-situation-actually-is&quot;&gt;What the NPU situation actually is&lt;/h2&gt;

&lt;p&gt;A quick note on the Intel AI Boost NPU since it kept coming up during my research. The NPU is only accessible through OpenVINO — which means Foundry Local is currently the only local inference engine that can use it from the options I tried. But as covered above, all the NPU-optimised models cap at 3,696 input tokens, which is nowhere near enough for the 21k Copilot CLI system prompt. This isn’t a software configuration problem — it’s the current limit of how the AI Boost NPU processes context in a single pass.&lt;/p&gt;

&lt;p&gt;So the NPU is off the table for this use case until Intel ships larger-context NPU models.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;what-i-tried-next-vllm-and-tgi-huggingface-inference-stack&quot;&gt;What I tried next: vLLM and TGI (HuggingFace inference stack)&lt;/h2&gt;

&lt;p&gt;After getting Ollama and LM Studio working, I wanted to see if the HuggingFace inference stack could do better. The native PyTorch kernels (AWQ-Marlin, FlashAttention v2) are purpose-built for this hardware and in theory should outperform llama.cpp’s GGUF decode. I tried two options: &lt;strong&gt;vLLM&lt;/strong&gt; and &lt;strong&gt;TGI (Text Generation Inference)&lt;/strong&gt;.&lt;/p&gt;

&lt;h3 id=&quot;the-format-split-gguf-vs-safetensors&quot;&gt;The format split: GGUF vs safetensors&lt;/h3&gt;

&lt;p&gt;Before going further, it’s worth understanding why these are separate ecosystems:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Ollama / LM Studio&lt;/strong&gt; → llama.cpp → GGUF only&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;vLLM / TGI&lt;/strong&gt; → PyTorch → HuggingFace safetensors only&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GGUF is the model file format used by llama.cpp — think of it as the packaging format for quantized models in the Ollama/LM Studio ecosystem. Safetensors is HuggingFace’s format, used by PyTorch-based runtimes like vLLM and TGI. Both support “4-bit” quantization but they’re completely different kernel implementations. You can’t load a GGUF file into vLLM or a safetensors file into Ollama.&lt;/p&gt;

&lt;p&gt;For Qwen2.5-7B on a 6 GB GPU, the right HuggingFace model is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Qwen/Qwen2.5-7B-Instruct-AWQ&lt;/code&gt; — an AWQ (Activation-aware Weight Quantization) 4-bit model, about 4.5 GB on disk.&lt;/p&gt;

&lt;h3 id=&quot;vllm-via-docker&quot;&gt;vLLM via Docker&lt;/h3&gt;

&lt;p&gt;vLLM is the most production-grade HuggingFace inference server. The official Docker image (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vllm/vllm-openai:latest&lt;/code&gt;) comes with CUDA, FlashAttention, and AWQ-Marlin kernels pre-built.&lt;/p&gt;

&lt;p&gt;Getting it to fit on a 6 GB GPU required a few tricks:&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;docker&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--runtime&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;nvidia&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--gpus&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;vllm-server&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;`
&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hf-cache:/root/.cache/huggingface&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;`
&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;127.0.0.1:8000:8000&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--shm-size&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;2g&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;`
&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;vllm/vllm-openai:latest&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;`
&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--model&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Qwen/Qwen2.5-7B-Instruct-AWQ&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--quantization&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;awq_marlin&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;`
&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--max-model-len&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;32768&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--gpu-memory-utilization&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;0.81&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;`
&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--kv-cache-dtype&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fp8&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--max-num-seqs&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;`
&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--cpu-offload-gb&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--enforce-eager&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;`
&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--dtype&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;0.0.0.0&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--port&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;8000&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Key constraints on a 6 GB card:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Free VRAM after CUDA runtime init is only 4.89 GiB&lt;/strong&gt; out of 5.97 GiB total — &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--gpu-memory-utilization 0.81&lt;/code&gt; is the cap&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;AWQ weights alone need 5.2 GiB on-GPU&lt;/strong&gt; → &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--cpu-offload-gb 2&lt;/code&gt; drops that to 3.17 GiB, freeing 1.25 GiB for KV cache&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--kv-cache-dtype fp8&lt;/code&gt;&lt;/strong&gt; halves KV footprint vs fp16 (fp8/fp16 = 8-bit vs 16-bit floating-point precision for stored values; lower precision = smaller memory footprint), required for 32k context&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-v hf-cache:/root/.cache/...&lt;/code&gt;&lt;/strong&gt; — use a named Docker volume, not a Windows bind mount. Loading from NTFS through VirtioFS takes 20+ minutes per model shard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;127.0.0.1:8000:8000&lt;/code&gt; not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;8000:8000&lt;/code&gt; — the unqualified form sometimes fails silently when old wslrelay processes still hold the port.&lt;/p&gt;

&lt;p&gt;The logs confirm FlashAttention v2 and AWQ-Marlin kernels loaded, which is great. But the benchmark result was sobering: &lt;strong&gt;~2.6 tok/s&lt;/strong&gt;. The CPU offload is the bottleneck — those 2 GiB of weight layers go through PCIe every single token. The architecture simply isn’t designed for this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;vLLM verdict:&lt;/strong&gt; ⚠️ Works but impractical on 6 GB. 2.6 tok/s is too slow for interactive CLI use. Natural fit is GPUs with ≥10 GB where the model fits fully in VRAM.&lt;/p&gt;

&lt;h3 id=&quot;tgi-text-generation-inference&quot;&gt;TGI (Text Generation Inference)&lt;/h3&gt;

&lt;p&gt;TGI is HuggingFace’s own inference server. It has native AWQ support and the official image is well-maintained. But there’s a hard blocker on 6 GB:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;AWQ weights = 5.2 GiB. GPU total = 5.97 GiB. After CUDA init there’s ~4.89 GiB free.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;TGI has no &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--cpu-offload-gb&lt;/code&gt; equivalent.&lt;/strong&gt; It must load the entire model into VRAM.&lt;/li&gt;
  &lt;li&gt;Result: OOM at KV cache allocation. The weights alone don’t fit, let alone any context.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There’s also a WSL2 Triton linker bug that needs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-e LIBRARY_PATH=/usr/local/cuda-12.4/compat&lt;/code&gt; to find &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;libcuda.so&lt;/code&gt;, but even with that fix the OOM is structurally unavoidable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TGI verdict:&lt;/strong&gt; ❌ OOM on 6 GB with this model and context size. TGI has no CPU offload path, so the full model must fit in VRAM. On larger GPUs (≥8 GB free after init) this wouldn’t be an issue.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;llamacpp--fastest-raw-cuda-inference&quot;&gt;llama.cpp — fastest raw CUDA inference&lt;/h2&gt;

&lt;p&gt;After publishing the initial results, I got a tip to try llama.cpp directly — not through LM Studio’s wrapper, but as a standalone server with the native CUDA build. LM Studio uses llama.cpp under the hood but adds process management overhead and restricts some server flags. Running llama.cpp directly gives you tighter control over Flash Attention, KV cache precision, and context window allocation.&lt;/p&gt;

&lt;h3 id=&quot;installation-cuda-build-on-windows&quot;&gt;Installation (CUDA build on Windows)&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;winget install ggml.llamacpp&lt;/code&gt; installs the &lt;strong&gt;Vulkan&lt;/strong&gt; build, not CUDA. For the CUDA build, download from the &lt;a href=&quot;https://github.com/ggml-org/llama.cpp/releases&quot;&gt;llama.cpp GitHub releases&lt;/a&gt; directly:&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$dest&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;LOCALAPPDATA&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;\llama.cpp&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;New-Item&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-ItemType&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Directory&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Force&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Path&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$dest&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Out-Null&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# CUDA 12.4 build — works fine with CUDA 13.0 driver (backward compatible)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Invoke-WebRequest&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://github.com/ggml-org/llama.cpp/releases/download/b9075/llama-b9075-bin-win-cuda-12.4-x64.zip&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-OutFile&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;TEMP&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;\llama-cuda.zip&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Invoke-WebRequest&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://github.com/ggml-org/llama.cpp/releases/download/b9075/cudart-llama-bin-win-cuda-12.4-x64.zip&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-OutFile&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;TEMP&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;\llama-cudart.zip&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Expand-Archive&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;TEMP&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;\llama-cuda.zip&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-DestinationPath&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$dest&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Force&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Expand-Archive&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;TEMP&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;\llama-cudart.zip&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-DestinationPath&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$dest&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Force&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Flash Attention syntax changed in build b9075+:&lt;/strong&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--flash-attn&lt;/code&gt; is no longer a bare flag. You must write &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--flash-attn on&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;starting-the-server&quot;&gt;Starting the server&lt;/h3&gt;

&lt;p&gt;llama.cpp loads GGUF files directly from the LM Studio model cache — no re-download needed:&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$model&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USERPROFILE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;\.lmstudio\models\bartowski\Qwen2.5-7B-Instruct-GGUF\Qwen2.5-7B-Instruct-Q4_K_S.gguf&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;LOCALAPPDATA&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;\llama.cpp\llama-server.exe&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--model&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$model&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--alias&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Qwen2.5-7B-Instruct-Q4_K_S&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;`
&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--n-gpu-layers&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;999&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--ctx-size&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;32768&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;`
&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--cache-type-k&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;q8_0&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--cache-type-v&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;q8_0&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;`
&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--flash-attn&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;`
&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;127.0.0.1&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--port&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;8088&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--cache-type-k q8_0 --cache-type-v q8_0&lt;/code&gt; flags compress the KV cache to 8-bit precision. At 32k context this saves ~896 MB — exactly the headroom needed to fit Q4_K_S weights alongside a 32k context in 6 GB VRAM:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;4.15 GB (weights) + 0.9 GB (q8_0 KV @ 32k) + ~0.4 GB (CUDA overhead) ≈ &lt;strong&gt;5.45 GB total&lt;/strong&gt; — ~550 MB headroom in 6 GB VRAM.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;copilot-cli-configuration&quot;&gt;Copilot CLI configuration&lt;/h3&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;COPILOT_PROVIDER_BASE_URL&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;         &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;http://127.0.0.1:8088/v1&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;COPILOT_MODEL&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;                      &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Qwen2.5-7B-Instruct-Q4_K_S&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;COPILOT_PROVIDER_MAX_PROMPT_TOKENS&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;32768&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# must match --ctx-size exactly&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;COPILOT_PROVIDER_MAX_OUTPUT_TOKENS&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;8192&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;COPILOT_PROVIDER_MAX_PROMPT_TOKENS&lt;/code&gt; must match your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--ctx-size&lt;/code&gt; exactly. Leaving it at the default 8192 while the server runs a 32k context gives:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;400 request (22704 tokens) exceeds the available context size (8192 tokens)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;result-230-toks-&quot;&gt;Result: 23.0 tok/s 🏆&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;23.0 tok/s&lt;/strong&gt; — faster than Ollama Qwen3-8B and nearly 2× faster than Ollama Qwen2.5-7B (12.3 tok/s). The same GGUF file, the same GPU, just running through llama.cpp’s direct CUDA path instead of Ollama’s management layer — with Flash Attention and 8-bit KV cache enabled.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;llama.cpp verdict:&lt;/strong&gt; ✅ New fastest setup at 23.0 tok/s. It reuses GGUF files already in your LM Studio model cache, so there’s no re-download needed — just the extra setup steps for the CUDA binary.&lt;/p&gt;

&lt;h3 id=&quot;bonus-vulkan-tensor-split-across-nvidia--intel-arc&quot;&gt;Bonus: Vulkan tensor split across NVIDIA + Intel Arc&lt;/h3&gt;

&lt;p&gt;I also tried the Vulkan build to see if offloading layers to the Intel Arc 140T’s 18 GB of shared memory would improve throughput. The device order on this machine:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Vulkan0: Intel Arc 140T  — 18 GB (shared system RAM)
Vulkan1: NVIDIA RTX PRO 500 — 6 GB dedicated GDDR7
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Results with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--tensor-split 1,4&lt;/code&gt; (≈20% Arc / ≈80% NVIDIA): &lt;strong&gt;22.1 tok/s&lt;/strong&gt; — essentially identical to NVIDIA-only Vulkan (22.3 tok/s) and slightly below the CUDA build (23.0 tok/s). No throughput improvement.&lt;/p&gt;

&lt;p&gt;The reason: “shared memory” on the Arc iGPU is system RAM accessed through the memory controller — roughly 10× slower than GDDR7. The model (4.15 GB) already fits fully in dedicated VRAM, so there are no layers to gain by involving the Arc. Adding inter-GPU synchronisation only adds overhead. The 18 GB number in Task Manager looks tempting, but it only helps when a model doesn’t fit in dedicated VRAM at all.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-powershell-profile-setup&quot;&gt;The PowerShell profile setup&lt;/h2&gt;

&lt;p&gt;After going through all of this I set up a clean way to switch between providers from any terminal session. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;set-copilot-local&lt;/code&gt; function gives you an interactive arrow-key menu to pick provider and model. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;online-copilot&lt;/code&gt; clears everything back to defaults.&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kr&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Invoke-InteractiveMenu&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;param&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$Title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]]&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$Options&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$selected&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$count&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$Options&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Count&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CursorVisible&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;$false&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$Title&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-ForegroundColor&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Cyan&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$i&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$i&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-lt&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$i&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-eq&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$selected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;  &amp;gt; &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$Options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-ForegroundColor&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Yellow&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;    &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$Options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-ForegroundColor&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Gray&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;while&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;$true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$key&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;$Host&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;UI&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;RawUI&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ReadKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;NoEcho,IncludeKeyDown&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$prev&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$selected&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;switch&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;VirtualKeyCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;38&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$selected&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-gt&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$selected&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;              &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Up&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$selected&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-lt&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$count&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$selected&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;     &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Down&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;13&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$selected&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;                   &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Enter&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$prev&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-ne&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$selected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$pos&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;$Host&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;UI&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;RawUI&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CursorPosition&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$pos&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Y&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$count&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;$Host&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;UI&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;RawUI&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CursorPosition&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$pos&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$i&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$i&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-lt&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$line&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$i&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-eq&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$selected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;  &amp;gt; &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$Options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                             &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;    &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$Options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$color&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$i&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-eq&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$selected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Yellow&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Gray&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$line&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;PadRight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;$Host&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;UI&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;RawUI&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WindowSize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Width&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-ForegroundColor&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$color&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;finally&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CursorVisible&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;$true&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Launch LM Studio (Vulkan backend pre-configured via hardware-config.json)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Start-LMStudio&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Start-Process&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;LOCALAPPDATA&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;\Programs\LM Studio\LM Studio.exe&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;LM Studio launched (Vulkan/Arc backend configured)&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-ForegroundColor&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Green&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Write the hardware-config.json that disables the NVIDIA GPU (Vulkan device 1)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# so llama.cpp Vulkan uses Arc 140T (device 0). Re-run after LM Studio reinstalls.&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Set-LMStudioArcGPU&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$hwConfig&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USERPROFILE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;\.lmstudio\.internal\hardware-config.json&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$json&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;{&quot;json&quot;:[[&quot;llama.cpp-win-x86_64-vulkan-avx2&quot;,{&quot;fields&quot;:[{&quot;key&quot;:&quot;load.gpuSplitConfig&quot;,&quot;value&quot;:{&quot;strategy&quot;:&quot;evenly&quot;,&quot;disabledGpus&quot;:[1],&quot;priority&quot;:[],&quot;customRatio&quot;:[]}}]}]],&quot;meta&quot;:{&quot;values&quot;:[&quot;map&quot;]}}&apos;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Set-Content&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$hwConfig&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$json&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Encoding&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;utf8&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;LM Studio: NVIDIA disabled, Arc 140T active&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-ForegroundColor&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Green&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;set-copilot-local&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$providerIdx&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Invoke-InteractiveMenu&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Title&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Select inference provider:&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Options&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;@(&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Ollama        (localhost:11434)&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Foundry Local (localhost:63839)&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;LM Studio     (localhost:1234)&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;vLLM / HuggingFace (127.0.0.1:8000)&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

    &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$providerIdx&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-eq&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Ollama — qwen2.5:7b-instruct-32k is a custom Modelfile with num_ctx 32768&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$baseUrl&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;http://localhost:11434/v1&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$models&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;@(&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PSCustomObject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]@{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;qwen2.5:7b-instruct-32k&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;PromptTokens&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32768&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;OutputTokens&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8192&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PSCustomObject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]@{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;llama3.1:8b&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;             &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;PromptTokens&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;131072&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;OutputTokens&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8192&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PSCustomObject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]@{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;llama3.2:3b&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;             &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;PromptTokens&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;131072&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;OutputTokens&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8192&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;elseif&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$providerIdx&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-eq&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Foundry Local — port is dynamic, detected from service status&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# WARNING: OpenVINO GPU models need Arc driver 32.0.101.8629 (not yet on Dell MC14250)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$statusOutput&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;foundry&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$foundryUrl&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$statusOutput&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Select-String&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Pattern&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;http://[\d.:]+&apos;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ForEach-Object&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;$_&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Matches&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Value&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Select-Object&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-First&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;TrimEnd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;/&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-not&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$foundryUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Foundry Local service is not running. Starting it...&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-ForegroundColor&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Yellow&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$startOutput&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;foundry&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Select-String&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Pattern&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;http://[\d.:]+&apos;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$foundryUrl&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$startOutput&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Matches&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Value?&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;TrimEnd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;/&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-not&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$foundryUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$foundryUrl&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;http://127.0.0.1:63839&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$baseUrl&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$foundryUrl&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/v1&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$models&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;@(&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PSCustomObject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]@{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;qwen2.5-1.5b-instruct-cuda-gpu:4&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;PromptTokens&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;28672&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;OutputTokens&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4096&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;elseif&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$providerIdx&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-eq&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# LM Studio — use 127.0.0.1 not localhost (LM Studio binds IPv4 only)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Always unload before load: lms load adds a :2 duplicate if any model is already running.&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# --gpu 0.78 = ~25/32 layers on NVIDIA GDDR (0-1 fraction, NOT layer count).&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$baseUrl&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;http://127.0.0.1:1234/v1&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$models&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;@(&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PSCustomObject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]@{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;qwen2.5-7b-instruct@q4_k_s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;PromptTokens&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32768&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;OutputTokens&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8192&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# 17.2 tok/s — fastest 7B&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PSCustomObject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]@{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;qwen2.5-7b-instruct@q5_k_m&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;PromptTokens&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32768&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;OutputTokens&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8192&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# 13.6 tok/s&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PSCustomObject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]@{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;gemma-4-e4b&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;                  &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;PromptTokens&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;65536&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;OutputTokens&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8192&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PSCustomObject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]@{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;qwen2.5-14b-instruct&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;         &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;PromptTokens&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32768&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;OutputTokens&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8192&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$lmsExe&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;LOCALAPPDATA&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;\Programs\LM Studio\resources\app\.webpack\lms.exe&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Test-Path&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$lmsExe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;  ⏹  Unloading existing models...&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-ForegroundColor&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;DarkGray&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$lmsExe&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unload&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--all&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;$null&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# vLLM (HuggingFace) — OpenAI-compatible on port 8000&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Requires a running vLLM container. ~2.6 tok/s on 6 GB (cpu-offload-gb 2). Best on GPUs ≥10 GB.&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Start the container first (startup takes ~5 min); check logs for &quot;Application startup complete&quot;.&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$baseUrl&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;http://127.0.0.1:8000/v1&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$models&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;@(&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PSCustomObject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]@{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Qwen/Qwen2.5-7B-Instruct-AWQ&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;PromptTokens&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32768&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;OutputTokens&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8192&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;$null&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Invoke-WebRequest&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;http://127.0.0.1:8000/v1/models&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-TimeoutSec&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-ErrorAction&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Stop&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Warning&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;vLLM not responding. Start the container first.&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

    &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$modelIdx&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Invoke-InteractiveMenu&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Title&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Select model:&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Options&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$models&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ForEach-Object&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;$_&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$selected&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$models&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$modelIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

    &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# For LM Studio: load the selected model with correct GPU split and context length&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$providerIdx&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-eq&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$lmsExe&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;LOCALAPPDATA&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;\Programs\LM Studio\resources\app\.webpack\lms.exe&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Test-Path&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$lmsExe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$ctxLen&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$selected&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;PromptTokens&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-ge&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;65536&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;65536&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;32768&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;  ▶  Loading &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$selected&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; --gpu 0.78 --context-length &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$ctxLen&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; ...&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-ForegroundColor&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;DarkYellow&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$lmsExe&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$selected&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--gpu&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;0.78&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--context-length&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$ctxLen&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

    &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;COPILOT_PROVIDER_BASE_URL&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;         &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$baseUrl&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;COPILOT_MODEL&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;                      &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$selected&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;COPILOT_PROVIDER_MAX_PROMPT_TOKENS&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$selected&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;PromptTokens&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;COPILOT_PROVIDER_MAX_OUTPUT_TOKENS&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$selected&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;OutputTokens&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

    &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$providerName&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;@(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Ollama&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Foundry Local&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;LM Studio&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;vLLM&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$providerIdx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Copilot provider set to local inference via [&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$providerName&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;] with model [&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$selected&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;]&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-ForegroundColor&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Green&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;online-copilot&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Remove-Item&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Env:COPILOT_PROVIDER_BASE_URL&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;          &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-ErrorAction&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;SilentlyContinue&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Remove-Item&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Env:COPILOT_MODEL&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;                       &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-ErrorAction&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;SilentlyContinue&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Remove-Item&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Env:COPILOT_PROVIDER_MAX_PROMPT_TOKENS&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-ErrorAction&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;SilentlyContinue&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Remove-Item&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Env:COPILOT_PROVIDER_MAX_OUTPUT_TOKENS&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-ErrorAction&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;SilentlyContinue&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Copilot provider set to online inference (default)&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-ForegroundColor&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Green&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The main functions that I use here is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;set-copilot-local&lt;/code&gt; for when I want to switch to local inference, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;online-copilot&lt;/code&gt; to switch back to the default online models in the same terminal session. By default this means Copilot still runs against the cloud models, so I don’t accidentally lose functionality in a random terminal window. But when I want to test local models, it’s just a quick &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;set-copilot-local&lt;/code&gt; and a couple of arrow keys to pick the provider and model.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;summary-of-what-works-today-april-2026&quot;&gt;Summary of what works today (April 2026)&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Option&lt;/th&gt;
      &lt;th&gt;Status&lt;/th&gt;
      &lt;th&gt;Notes&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;online-copilot&lt;/code&gt; (GitHub-hosted Claude/GPT)&lt;/td&gt;
      &lt;td&gt;✅ Best&lt;/td&gt;
      &lt;td&gt;Default, no setup needed&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;llama.cpp &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qwen2.5-7b-instruct@q4_k_s&lt;/code&gt;, CUDA, 32k, q8_0 KV&lt;/td&gt;
      &lt;td&gt;✅ Works&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;23.0 tok/s&lt;/strong&gt; — fastest local model 🏆&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Ollama &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qwen3:8b&lt;/code&gt; on NVIDIA&lt;/td&gt;
      &lt;td&gt;✅ Works&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;22.1 tok/s&lt;/strong&gt; — fastest local model before llama.cpp&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;LM Studio &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qwen2.5-7b-instruct@q4_k_s&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--gpu 0.78&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;✅ Works&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;15.2 tok/s&lt;/strong&gt; — fastest 7B in LM Studio&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;LM Studio &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qwen2.5-7b-instruct@q5_k_m&lt;/code&gt;, 25 layers NVIDIA + CPU&lt;/td&gt;
      &lt;td&gt;✅ Works&lt;/td&gt;
      &lt;td&gt;13.6 tok/s&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Ollama &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qwen2.5:7b-instruct-32k&lt;/code&gt; on NVIDIA&lt;/td&gt;
      &lt;td&gt;✅ Works&lt;/td&gt;
      &lt;td&gt;12.3 tok/s&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;vLLM &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Qwen2.5-7B-Instruct-AWQ&lt;/code&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--cpu-offload-gb 2&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;⚠️ Too slow&lt;/td&gt;
      &lt;td&gt;2.6 tok/s — CPU offload bottleneck on 6 GB&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;TGI &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Qwen2.5-7B-Instruct-AWQ&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;❌ OOM&lt;/td&gt;
      &lt;td&gt;No CPU offload support; 5.2 GiB weights won’t fit&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Foundry Local on Arc 140T (OpenVINO GPU)&lt;/td&gt;
      &lt;td&gt;❌ Blocked&lt;/td&gt;
      &lt;td&gt;Needs Arc driver 32.0.101.8629 from Dell&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Foundry Local on NPU&lt;/td&gt;
      &lt;td&gt;❌ Too small&lt;/td&gt;
      &lt;td&gt;3,696 token limit, CLI needs ~21k&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Foundry Local CUDA 7B on NVIDIA&lt;/td&gt;
      &lt;td&gt;❌ OOM crash&lt;/td&gt;
      &lt;td&gt;6 GB isn’t enough&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;LM Studio on Arc 140T only (Vulkan)&lt;/td&gt;
      &lt;td&gt;❌ Too slow&lt;/td&gt;
      &lt;td&gt;Saturates shared memory bus, freezes machine&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;throughput-comparison&quot;&gt;Throughput comparison&lt;/h2&gt;

&lt;p&gt;Speed matters for interactive use. The Copilot CLI generates structured JSON for tool calls, thinks through multi-step plans, and writes code — all of which require hundreds of tokens of output per turn. Anything below ~8 tok/s starts to feel noticeably slow for a CLI tool.&lt;/p&gt;

&lt;p&gt;I measured output throughput (tokens per second) using a standardised benchmark: each provider was asked to generate a ~250-token PowerShell function, at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;temperature=0.2&lt;/code&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;max_tokens=400&lt;/code&gt;, repeated 3 times after a warm-up run. The warm-up run loads the model into VRAM and warms the KV cache; only the subsequent timed runs are averaged.&lt;/p&gt;

&lt;p&gt;The test prompt is a fixed PowerShell coding task so every provider generates a roughly comparable amount of output. This measures &lt;strong&gt;output throughput&lt;/strong&gt; (decode speed) — which is what you feel as the response streaming in — not prompt processing speed or time-to-first-token.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;⚠️ &lt;strong&gt;Not a perfect apples-to-apples comparison for all rows.&lt;/strong&gt; The 1.5B rows are a separate size class — they’re fast because of the smaller model, not the runtime. The Qwen3-8B row uses a newer model architecture; the 7B rows (LM Studio and Ollama) use the same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qwen2.5-7b-instruct&lt;/code&gt; model in different quant/format variants and are directly comparable. Foundry Local runs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qwen2.5-1.5b-instruct&lt;/code&gt; — the only model that fits on the hardware today. vLLM uses AWQ format but the same model family.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The script is available as a Gist: &lt;a href=&quot;https://gist.github.com/rajbos/8c9a5bfb832469db52482082f88aae06&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Measure-LocalAIThroughput.ps1&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Provider&lt;/th&gt;
      &lt;th&gt;Model&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Avg tok/s&lt;/th&gt;
      &lt;th&gt;Notes&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Foundry Local&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qwen2.5-1.5b-instruct-cuda-gpu:4&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;117&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;NVIDIA RTX PRO 500 CUDA — &lt;strong&gt;1.5B model&lt;/strong&gt; (not comparable to 7B/8B rows)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Ollama (1.5B)&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qwen2.5:1.5b-instruct-32k&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;55.3&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;NVIDIA RTX PRO 500 CUDA, 32k context — &lt;strong&gt;1.5B model&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;LM Studio (1.5B)&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qwen2.5-coder-1.5b-instruct&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;49.8&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;NVIDIA RTX PRO 500 CUDA full offload — &lt;strong&gt;1.5B model&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;llama.cpp (CUDA)&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qwen2.5-7b-instruct@q4_k_s&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;23.0&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;CUDA, Flash Attention, 32k ctx, q8_0 KV cache — &lt;strong&gt;fastest 7B overall&lt;/strong&gt; 🏆&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Ollama (Qwen3-8B)&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qwen3:8b&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;22.1&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;NVIDIA RTX PRO 500 CUDA, 32k context&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;llama.cpp (Vulkan/NVIDIA)&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qwen2.5-7b-instruct@q4_k_s&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;22.3&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Vulkan build, NVIDIA RTX PRO 500 only&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;llama.cpp (Vulkan/split)&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qwen2.5-7b-instruct@q4_k_s&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;22.1&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Vulkan, tensor-split 1:4 across Arc + NVIDIA — no benefit vs NVIDIA-only&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;LM Studio (7B)&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qwen2.5-7b-instruct@q4_k_s&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;15.2&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--gpu 0.78&lt;/code&gt; (25/32 layers NVIDIA GDDR), 32k context&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Ollama (7B)&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qwen2.5:7b-instruct-32k&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;12.3&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;NVIDIA RTX PRO 500 CUDA, 32k context&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;vLLM (Docker)&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Qwen2.5-7B-Instruct-AWQ&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;2.5&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;AWQ-Marlin + FlashAttention v2; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--cpu-offload-gb 2&lt;/code&gt; bottleneck on 6 GB&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;A few things to note from these numbers:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Foundry Local at 117 tok/s&lt;/strong&gt; looks spectacular but it’s running a 1.5B model — not capable enough for agentic tasks. Still, it’s a preview of what the numbers could look like once the Arc 140T OpenVINO path is unblocked: running a proper 7B model on 16 GB VRAM with no memory pressure.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;llama.cpp CUDA at 23.0 tok/s&lt;/strong&gt; is the new overall winner. The same Q4_K_S GGUF that LM Studio runs, launched directly via llama.cpp with Flash Attention and 8-bit KV cache — no management overhead, no restricted flags. Nearly 2× faster than Ollama Qwen2.5-7B on the same hardware.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Ollama Qwen3-8B at 22.1 tok/s&lt;/strong&gt; is the runner-up. Qwen3-8B is a newer architecture than Qwen2.5-7B and fits fully into the 6 GB NVIDIA VRAM (Q4_K_M = 4.68 GB, leaving room for KV cache and overhead). Ollama handles the 128k-capable model natively without needing a custom Modelfile. LM Studio’s Q4_K_M variant fails to load — the 4.68 GB weights plus LM Studio’s per-process overhead pushes it over the 6 GB limit; Q3 quantisation would be needed there.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;llama.cpp Vulkan tensor split at 22.1 tok/s&lt;/strong&gt; is essentially identical to NVIDIA-only Vulkan (22.3 tok/s). The Intel Arc 140T’s “18 GB shared memory” is system RAM — adding it only introduces inter-GPU synchronisation overhead when the model already fits in dedicated VRAM.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;LM Studio Q4_K_S at 15.2 tok/s&lt;/strong&gt; remains the fastest GUI-based option and a solid fallback. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--gpu 0.78&lt;/code&gt; flag is the key — without it, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--gpu max&lt;/code&gt; silently falls back to CPU when the model + 32k KV cache don’t fit together, dropping to ~2 tok/s.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;LM Studio Q5_K_M at 13.6 tok/s&lt;/strong&gt; and &lt;strong&gt;Ollama 7B at 12.3 tok/s&lt;/strong&gt; are the most stable lower-tier options. Ollama’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OLLAMA_FLASH_ATTENTION=1&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OLLAMA_KV_CACHE_TYPE=q8_0&lt;/code&gt; settings are important — without them the KV cache alone takes 1.75 GB of VRAM, reducing model layers on GPU and dropping throughput to ~9 tok/s.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;vLLM at 2.5 tok/s&lt;/strong&gt; uses AWQ-Marlin kernels and FlashAttention v2 — theoretically the fastest inference stack available. But on 6 GB you need &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--cpu-offload-gb 2&lt;/code&gt;, which means 2 GiB of weight layers cross the PCIe bus every single token. The framework overhead cancels out the kernel advantage entirely.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;You cannot run Ollama and LM Studio at the same time&lt;/strong&gt; on this machine. Both want to load the model weights into NVIDIA’s 6 GB VRAM. The benchmark script handles this automatically.&lt;/li&gt;
  &lt;li&gt;For context: online Claude 3.5 Sonnet generates at 80–100+ tok/s. Local 7B/8B models at 12–23 tok/s are usable but noticeably slower for multi-step agentic work.&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;fastest-local-setup-for-this-machine&quot;&gt;Fastest local setup for this machine&lt;/h2&gt;

&lt;p&gt;Based on all the testing, the fastest practical setup for running GitHub Copilot CLI locally on this Dell Pro Max 14 is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;llama.cpp (CUDA) with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qwen2.5-7b-instruct@q4_k_s&lt;/code&gt;&lt;/strong&gt; → &lt;strong&gt;23.0 tok/s&lt;/strong&gt; 🏆&lt;/p&gt;

&lt;p&gt;The key steps:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Download the CUDA build from &lt;a href=&quot;https://github.com/ggml-org/llama.cpp/releases&quot;&gt;llama.cpp releases&lt;/a&gt; and extract to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%LOCALAPPDATA%\llama.cpp\&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Start the server: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;llama-server.exe --model &amp;lt;path-to-gguf&amp;gt; --n-gpu-layers 999 --ctx-size 32768 --cache-type-k q8_0 --cache-type-v q8_0 --flash-attn on --host 127.0.0.1 --port 8088&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Set environment variables: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$env:COPILOT_PROVIDER_BASE_URL = &quot;http://127.0.0.1:8088/v1&quot;&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$env:COPILOT_MODEL = &quot;Qwen2.5-7B-Instruct-Q4_K_S&quot;&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$env:COPILOT_PROVIDER_MAX_PROMPT_TOKENS = &quot;32768&quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The GGUF file can be reused from your LM Studio model cache — no re-download needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Easiest local setup:&lt;/strong&gt; If you don’t want to manage a separate server process, &lt;strong&gt;Ollama with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qwen3:8b&lt;/code&gt;&lt;/strong&gt; → &lt;strong&gt;22.1 tok/s&lt;/strong&gt; is the runner-up with a much simpler setup: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;winget install Ollama.Ollama&lt;/code&gt; + &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ollama pull qwen3:8b&lt;/code&gt; + &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;set-copilot-local&lt;/code&gt;. No binary downloads, no server management, and only ~5% slower.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Real-world caveat:&lt;/strong&gt; These numbers are from a synthetic benchmark (3 runs × 400 tokens, fixed prompt). Real Copilot CLI sessions involve longer prompts, tool calls with JSON parsing, and multi-turn context accumulation — all of which affect throughput differently. I’ll update this section after extended real-world use.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;final-verdict-on-local-inference-options-for-github-copilot-cli-on-this-machine&quot;&gt;Final verdict on local inference options for GitHub Copilot CLI on this machine:&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;llama.cpp (CUDA) with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qwen2.5-7b-instruct@q4_k_s&lt;/code&gt;&lt;/strong&gt; is the best local option today at &lt;strong&gt;23.0 tok/s&lt;/strong&gt; — beating Ollama Qwen3-8B (22.1 tok/s) and nearly 2× faster than Ollama Qwen2.5-7B (12.3 tok/s). The key insight is that llama.cpp’s direct CUDA path with Flash Attention and 8-bit KV cache extracts more throughput from the same hardware and model file than any management layer on top. If you prefer a simpler setup, Ollama Qwen3-8B at 22.1 tok/s is an excellent runner-up with a one-line install. LM Studio is a solid GUI fallback at 15.2 tok/s. The HuggingFace stack (vLLM, TGI) is currently impractical on 6 GB due to the need for CPU offload or lack of it, respectively.&lt;/p&gt;

&lt;p&gt;Working with this from the Copilot CLI is workable, and of course it cannot compare to the powerful models that the hosting providers can run in the cloud. But for basic agentic tasks, code generation, and tool calling, it’s a workable local setup.&lt;/p&gt;

&lt;h2 id=&quot;what-im-waiting-for&quot;&gt;What I’m waiting for&lt;/h2&gt;

&lt;p&gt;The most promising path — Foundry Local with OpenVINO on the Arc 140T — is blocked by a single driver update. The Arc 140T has 16 GB of dedicated VRAM with Intel’s optimised INT4 kernels via OpenVINO, and the 7B models should run fast and stable with no memory pressure. Once Dell publishes Arc driver 32.0.101.8629 for the MC14250, that’s the first thing I’m testing.&lt;/p&gt;

&lt;p&gt;Until then, llama.cpp (CUDA) with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qwen2.5-7b-instruct@q4_k_s&lt;/code&gt; is the fastest local option at 23.0 tok/s — or Ollama &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qwen3:8b&lt;/code&gt; at 22.1 tok/s if you prefer a simpler setup. And for anything where quality actually matters, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;online-copilot&lt;/code&gt; is still the right call.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Show you Microsoft Certification badges in Credly</title>
			<link href="https://devopsjournal.io/blog/2026/02/02/Link-MS-certification-to-Credly"/>
			<updated>2026-02-02T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2026/02/02/Link-MS-certification-to-Credly</id>
			<content type="html">&lt;p&gt;Microsoft has stopped their partnership with Credly for hosting certification badges and instead centralized everything to the Microsoft Learn platform. However, if you still want to showcase your Microsoft Certification badges in Credly, you can do so by following these steps:&lt;/p&gt;

&lt;h2 id=&quot;microsoft-learn-steps&quot;&gt;Microsoft Learn steps&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;Log in to your &lt;a href=&quot;https://learn.microsoft.com/&quot;&gt;Microsoft Learn&lt;/a&gt; account.&lt;/li&gt;
  &lt;li&gt;Navigate to the “Credentials” section of your profile.&lt;/li&gt;
  &lt;li&gt;Open the “view all” link to see all of your certifications in case the new certification is not immediately visible (I have two renewals that took over the list).&lt;/li&gt;
  &lt;li&gt;So do not use the “Past exams” view, that will not help&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;/images/2026/20260202/20260202_01_CertificationOverview.png&quot; alt=&quot;Screenshot of the Microsoft Learn Credentials page showing certifications&quot; /&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Find the certification you want to add to Credly and click on “View certification details”.&lt;/li&gt;
  &lt;li&gt;Copy the url from the browser.&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;credly-steps&quot;&gt;Credly steps&lt;/h1&gt;
&lt;ol&gt;
  &lt;li&gt;Log in to your &lt;a href=&quot;https://www.credly.com/&quot;&gt;Credly&lt;/a&gt; account.&lt;/li&gt;
  &lt;li&gt;Navigate to your profile in the acccount drop down menu.&lt;/li&gt;
  &lt;li&gt;Click on “Upload other badges” button.&lt;/li&gt;
  &lt;li&gt;Use the badge url option at hte bottom of the screen and paste in the url you copied from Microsoft Learn.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;/images/2026/20260202/20260202_02_CredlyAddBadge.png&quot; alt=&quot;Screenshot of the Credly &amp;quot;Upload other badges&amp;quot; page showing the badge url option&quot; /&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Click on “Add Badge” button and wait for the validation to complete.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Wait until the actual bafdge shows up in your Credly profile, takes over an hour in my experience.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>GitHub Copilot - Coding Agent Examples Walkthrough</title>
			<link href="https://devopsjournal.io/blog/2025/12/20/Copilot-Agent-example"/>
			<updated>2025-12-20T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2025/12/20/Copilot-Agent-example</id>
			<content type="html">&lt;p&gt;In this post, I will walk you through an example of different ways to use the GitHub Copilot Coding Agent to automate a coding task. The Coding Agent is a powerful feature that leverages AI to help you write, review, and refactor code more efficiently. It uses a prompt coming from one of the locations below and will then have a runtime inside of a secured GitHub Actions environment to execute the steps it needs to take to complete your request, with for example a locked down network configuration to prevent it from doing weird things in your name. If you want to know more about the architecture and security model, check out the official documentation: &lt;a href=&quot;https://docs.github.com/en/copilot/concepts/agents/coding-agent/about-coding-agent#built-in-security-protections&quot;&gt;GitHub Copilot Coding Agent&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Places where you can start a Coding Agent session from:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Your editor&lt;/li&gt;
  &lt;li&gt;Agent Task panel in a repo context&lt;/li&gt;
  &lt;li&gt;Repository creation through the UI&lt;/li&gt;
  &lt;li&gt;Chat interface in github.&lt;/li&gt;
  &lt;li&gt;GitHub Phone app&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I will show some examples of these below.&lt;/p&gt;

&lt;h2 id=&quot;your-editor&quot;&gt;Your editor&lt;/h2&gt;
&lt;p&gt;When you are in a Copilot Chat conversation you can gather all the context you need and then write the next prompt which can then be “hand off to Cloud Agent”.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; At the end of December 2025 when this post was written, this feature was only available in VS Code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is an example screenshot of that experience when staring in Plan Mode and handing it off to Agent Mode:
&lt;img src=&quot;/images/2025/20251220/20251220_03_ChatContinueInCloud.png&quot; alt=&quot;Screenshot that shows a &amp;quot;continue in cloud&amp;quot; in the hand off from Plan mode to Agent Mode&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Here is an example from the chat in Agent Mode itself:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2025/20251220/20251220_04_ChatContinueInCloud.png&quot; alt=&quot;Screenshot that shows the same &amp;quot;Continue in cloud&amp;quot; button in the chat in Agent Mode&quot; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: The “Continue in Background” button, which will let you start the same prompt with context in the Copilot CLI, which will execute in the background and notify you when done.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;agent-task-panel-in-a-repo-context&quot;&gt;Agent Task panel in a repo context&lt;/h2&gt;
&lt;p&gt;On github.com you have an “Agent Tasks” tab in the UI, which allows you to start a Coding Agent session in the context of that repository. This is useful when you want to automate tasks related to that specific codebase. I use this all the time to let it work on for example a failed GitHub Actions workflow, where I can be very specific about what I want it to do.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2025/20251220/20251220_01_AgentTaskPanel.png&quot; alt=&quot;Screenshot of the agent panel&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Some example prompts that I regularly use in this context:&lt;/p&gt;

&lt;h3 id=&quot;1-fix-the-failing-workflow&quot;&gt;1. Fix the failing workflow&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Look at the failing workflow here &amp;lt;link to the url for the workflow&amp;gt; or just &amp;lt;name of the workflow or job&amp;gt;. Find out why this is failing first and define if this error makes sense. If we need to fix something in the code or the workflow, do so. If we should handle this differently, explain what we should do and suggest a better approach.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;2-check-multiple-workflow-runs&quot;&gt;2. Check multiple workflow runs&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Look at the last two workflow runs for &amp;lt;name of the workflow&amp;gt;. I notice that the number of executions in the log stays the same, so it seems we are not getting new data. Find out why this is happening and fix the code or workflow to ensure we get new data on each run.    
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;3-add-extra-functionality-to-the-repo&quot;&gt;3. Add extra functionality to the repo:&lt;/h3&gt;

&lt;p&gt;I have an example repo for hosting your own Private MCP Registry which is needed in especially an Enterprise setting. I got a question from someone how you would add a stdio type of MCP server to the configuration. So I started working on a prompt for that:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;We need an example how to register a stdio MCP server. Could you please add a working example for playwright mcp server(stdio MCP server).
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Because I know it is helpful to give it some extra context, I also added this to the prompt after doing a quick web search for the relevant documentation:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Here is the normal MCP servers.json config for client side configuration:

{
  &quot;mcpServers&quot;: {
    &quot;playwright&quot;: {
      &quot;type&quot;: &quot;local&quot;,
      &quot;command&quot;: &quot;npx&quot;,
      &quot;tools&quot;: [
        &quot;*&quot;
      ],
      &quot;args&quot;: [
        &quot;@playwright/mcp@latest&quot;
      ]
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Since my repository also has a servers.json in it, I wanted to prevent the model from getting confised by that, so I find it a good idea to add just a bit more context by checking the MCP Private Regstry documentation. I quick web search brought me to the GitHub repository for the MCP Registry: &lt;a href=&quot;https://github.com/modelcontextprotocol/registry&quot;&gt;modelcontextprotocol/registry&lt;/a&gt;. Instead of looking for things myself, I asked the Copilot Web Chat to find the relevant documentation for me:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2025/20251220/20251220_02_WebChat.png&quot; alt=&quot;Screenshot of the webchat with this prompt: &amp;quot;is there an example in this repo for how to congfiugure stdio servers in the registry?&amp;quot; The result shows the file with the documentation&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This brought me to the right file: &lt;a href=&quot;https://github.com/modelcontextprotocol/registry/blob/9afbaacdfdf8966d73de09a795076fb0386c5c3d/docs/reference/server-json/generic-server-json.md#L39-L62&quot;&gt;docs/reference/server-json/generic-server-json.md#L39-L62&lt;/a&gt; with even the relevant line numbers included!&lt;/p&gt;

&lt;p&gt;So this extra context was added to the prompt as well:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Use the example from: https://github.com/modelcontextprotocol/registry/blob/9afbaacdfdf8966d73de09a795076fb0386c5c3d/docs/reference/server-json/generic-server-json.md#L39-L62
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The result of that PR can be found here: &lt;a href=&quot;https://github.com/rajbos/mcp-registry-demo/pull/12&quot;&gt;PR link&lt;/a&gt;. Note the original prompt is shown in the PR body as well. I apreciate that a lot as it shows how it transformed the PR goal from my initial prompt by leveraging additional information it found during the execution. It can also be a great way to learn how to write better prompts by seeing how it interpreted your original request, or learn how other folks approach this.&lt;/p&gt;

&lt;h2 id=&quot;repository-creation-through-the-ui&quot;&gt;Repository creation through the UI&lt;/h2&gt;

&lt;p&gt;A lot of folks have not seen this yet, but when creating a new repository through the GitHub UI, you can also add a prompt for the Coding Agent to execute right away in the new repository context. The new repo will be created with the things you configure, and then Coding Agent will start working on your prompt right away by creating a new PR. This is super useful for bootstrapping new projects with specific requirements. Do note at the bottom left that you only have 500 characters for the prompt. I use this for initial scaffolding all the time.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2025/20251220/20251220_05_NewRepo.png&quot; alt=&quot;Screenshot of the new repository creation through the GitHub UI&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;chat-interface-in-githubcom&quot;&gt;Chat interface in github.com&lt;/h2&gt;

&lt;p&gt;Last but not least, you can also start a Coding Agent session directly from the Chat interface on github.com. This is useful when you want to have a conversation about a specific coding task and then hand it off to the Coding Agent to execute it. You can provide context in the chat and then use the “Continue in Cloud” button to start the Coding Agent session.&lt;/p&gt;

&lt;p&gt;I use this either from the Chat buton in the top bar to find information in the repo, setup my context, and then prompt with something like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Please create a new branch and add a GitHub Actions workflow that does X, Y, and Z. Make sure to follow best practices and include any necessary secrets or configurations.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Or just simply:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Hand off this task to Coding Agent. 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will trigger a background call with the conversation of you current session, and send that into the Coding Agent. Often the UI will either tell you the hand off was successful (not always does it ask for confirmation if it needs to create the task, seems to be dependent on where you trigger this task from), or not.&lt;/p&gt;

&lt;p&gt;Also note that the chat window then often shows a direct button to go to that new agent session, but that is not always the case. Seems to be linked to the place where you trigger this from or the prompt itself.&lt;/p&gt;

&lt;p&gt;If you do not see that button, you can always go to the “Agent Tasks” button in the top bar to find your new task, or go to the Copilot/Agents page.&lt;/p&gt;

&lt;h2 id=&quot;github-phone-app&quot;&gt;GitHub Phone app&lt;/h2&gt;
&lt;p&gt;The phone app of GitHub Copilot lets you do exactly the same as the web chat interface on github.com. You can have a conversation in the chat, and then hand it off to the Coding Agent to execute your request in the context of the repository you are working with, or just directly start a new Coding Agent session from the Agent Tasks panel on the phone app:&lt;br /&gt;
&lt;img src=&quot;/images/2025/20251220/20251220_06_Phone.jpg&quot; alt=&quot;Screenshot of the phone app on iOS&quot; /&gt;&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>GitHub Copilot Premium Requests</title>
			<link href="https://devopsjournal.io/blog/2025/06/17/Copilot-premium-requests"/>
			<updated>2025-06-17T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2025/06/17/Copilot-premium-requests</id>
			<content type="html">&lt;p&gt;Some important changes are happening, which means you will need to start paying for the amount of Generative AI you use with GitHub Copilot. This will finally make the end-user think about the monetary cost of executing a request with a Large Language Model, so they realize this stuff is not running for free. In that sense we have been spoiled, so it is time to take up some ownership here for the end-users. This post will give you an overview of what you need to expect and how you can protect yourself from overspending.&lt;/p&gt;

&lt;h1 id=&quot;what-is-premium-requests-for-github-copilot-&quot;&gt;What is Premium Requests for GitHub Copilot ?&lt;/h1&gt;
&lt;p&gt;Tomorrow the 18th is the date GitHub Copilot &lt;a href=&quot;https://docs.github.com/en/copilot/managing-copilot/monitoring-usage-and-entitlements/about-premium-requests&quot;&gt;Premium Requests&lt;/a&gt; will be enforced (see the docs &lt;a href=&quot;https://docs.github.com/en/copilot/managing-copilot/monitoring-usage-and-entitlements/about-premium-requests&quot;&gt;here&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2025/20250617/20250617_Multipliers.png&quot; alt=&quot;Screenshot of the different multipliers per model&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This post gives you an overview to know what you need to expect. Copilot Premium Requests are any request that is made to any model that is not the default (currently GPT-4o and GPT-4.1). Some features against a model will cost 1 Premium Request, some will cost more then 1 Premium Request (using GPT-4.5 will be x50!) and some are less expensive (Gemini 2.0 Flash is the cheapest at 0.25x).&lt;/p&gt;

&lt;p&gt;I previously recorded a video on Premium Requests as well, find it &lt;a href=&quot;https://github-copilot.xebia.ms/detail?videoId=43&quot;&gt;here&lt;/a&gt; if you prefer to learn things that way:&lt;br /&gt;
&lt;a href=&quot;https://github-copilot.xebia.ms/detail?videoId=43&quot;&gt;&lt;img src=&quot;/images/2025/20250617/20250617_Video.png&quot; alt=&quot;Screenshot of the GitHub Copilot Premium Requests documentation&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;what-is-a-premium-request&quot;&gt;What is a premium request&lt;/h1&gt;
&lt;p&gt;Here is an overview of the main features that will consume a premium request:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Chatting with a non default model: 1 premium request per ‘turn’ (every question and answer is a turn)&lt;/li&gt;
  &lt;li&gt;Every step in the Coding Agent: 1 premium request. The agent can decide to make multiple steps, and will thus consume a premium request for each step. Note: I hope that the agent will also come with a “max requests” setting, so that you can limit the amount of requests it can make in a single conversation and prevent it from overspending.&lt;/li&gt;
  &lt;li&gt;Requesting a Code Review in a Pull Request: 1 premium request&lt;/li&gt;
  &lt;li&gt;Agent Mode in an editor: 1 premium request per user initiated request&lt;/li&gt;
  &lt;li&gt;Copilot Coding Agent: use 1 premium requests per time you trigger it. Starting the task is a trigger. Adding a comment afterwards (and thus starting a follow up task) is a trigger and thus an extra premium request.&lt;/li&gt;
  &lt;li&gt;From the docs &lt;a href=&quot;https://docs.github.com/en/copilot/managing-copilot/understanding-and-managing-copilot-usage/understanding-and-managing-requests-in-copilot&quot;&gt;here&lt;/a&gt;, Copilot Spaces and Extensions also consume premium requests, but it is not clear how many requests they consume or for what. I can imagine that a Chat Turn against a Copilot Space will consume a premium request each time, and same for Copilot Extensions. For the latter it is also not clear if that is for both local extensions (like the @Azure extension that runs locally inside of VS Code), or also for the remote extensions that are installed as GitHub Apps in your organization.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;free-plan&quot;&gt;Free plan&lt;/h2&gt;
&lt;p&gt;If you are on a Free plan, even the base model will consume a premium request if you are using the Chat feature.&lt;/p&gt;

&lt;h2 id=&quot;paid-plans&quot;&gt;Paid plans&lt;/h2&gt;
&lt;p&gt;If you are on a paid plan, then you will get a certain amount of premium requests per month. If you go over this amount, you will be charged for the extra requests at $0.04 per premium request. If that request is against a 50x model, then you will be charged $2.00 for that single request! To view the full overview of the different multipliers, see the &lt;a href=&quot;https://docs.github.com/en/copilot/managing-copilot/monitoring-usage-and-entitlements/about-premium-requests&quot;&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is the table of the different amount of included premium requests per plan:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Plan&lt;/th&gt;
      &lt;th&gt;Premium requests&lt;/th&gt;
      &lt;th&gt;Copilot Chat in IDEs&lt;/th&gt;
      &lt;th&gt;Code completion&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Copilot Free&lt;/td&gt;
      &lt;td&gt;50 per month&lt;/td&gt;
      &lt;td&gt;50 messages per month&lt;/td&gt;
      &lt;td&gt;2000 completions per month&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Copilot Pro&lt;/td&gt;
      &lt;td&gt;300 per month&lt;/td&gt;
      &lt;td&gt;Unlimited with base model&lt;/td&gt;
      &lt;td&gt;Unlimited&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Copilot Pro+&lt;/td&gt;
      &lt;td&gt;1500 per month&lt;/td&gt;
      &lt;td&gt;Unlimited with base model&lt;/td&gt;
      &lt;td&gt;Unlimited&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Copilot Business&lt;/td&gt;
      &lt;td&gt;300 per user per month&lt;/td&gt;
      &lt;td&gt;Unlimited with base model&lt;/td&gt;
      &lt;td&gt;Unlimited&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Copilot Enterprise&lt;/td&gt;
      &lt;td&gt;1000 per user per month&lt;/td&gt;
      &lt;td&gt;Unlimited with base model&lt;/td&gt;
      &lt;td&gt;Unlimited&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;To protect yourself or your users, you can configure a &lt;a href=&quot;https://docs.github.com/en/billing/managing-your-billing/preventing-overspending&quot;&gt;budget&lt;/a&gt; for premium requests in your user/organization settings. The default is $0.00, but you can set it to any amount you want. If you reach this budget, then all premium requests will be blocked until the next month.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2025/20250617/20250618_CopilotBudget.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This is now finally available inside of the new Coding Agent session panel as well: 
&lt;img src=&quot;/images/2025/20250617/20250618_CodingAgent.png&quot; alt=&quot;Screenshot showing that coding agent ran for 14 minutes and consumed 54 premium request&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;finding-your-own-usage-info-as-a-user&quot;&gt;Finding your own usage info as a User&lt;/h1&gt;
&lt;p&gt;The changes that where made where showing the multipliers to the different models to the users so they can make decisions on which model to use:&lt;br /&gt;
&lt;img src=&quot;/images/2025/20250617/20250618_CopilotModelSelection.png&quot; alt=&quot;Screenshot of the different multipliers per model&quot; /&gt;&lt;br /&gt;
This is now visible for at least Visual Studio Code, Jetbrains IDE’s, and Visual Studio.
These editors also show the setup for your account:&lt;br /&gt;
&lt;img src=&quot;/images/2025/20250617/20250618_CopilotOverview.png&quot; alt=&quot;Screenshot of the setup for your account in Visual Studio Code&quot; /&gt;&lt;br /&gt;
If you look closely, you can even see the progress bar in the middle of the screen showing that I have been using some Premium Requests already. Visual Studio shows it in a slightly different way by using the GitHub Copilot icon in the top right corner:
&lt;img src=&quot;/images/2025/20250617/20250618_VisualStudioUsed.png&quot; alt=&quot;Screenshot of the amount of premium requests used in Visual Studio&quot; /&gt;&lt;/p&gt;

&lt;p&gt;To find your own usage information, you can go to your &lt;a href=&quot;https://github.com/settings/billing&quot;&gt;User –&amp;gt; Settings –&amp;gt; Billing&lt;/a&gt; and then get an overview like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2025/20250617/20250618_UserUsage.png&quot; alt=&quot;Screenshot of user usage information showing the cost of your Copilot and Premium Requests usage in the current period&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;analyzing-the-github-copilot-premium-requests-report-in-your-organization--enterprise&quot;&gt;Analyzing the GitHub Copilot Premium Requests report in your organization / enterprise&lt;/h1&gt;
&lt;p&gt;Do you need to analyze the GitHub Copilot Premium requests CSV now that they will be enforced? I created a single page application (SPA) with GitHub Spark and GitHub Copilot Coding Agent to display an overview of the Premium Requests CSV that you can currently download (no API yet 😓).&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Can be hosted on GitHub Pages:  GitHub Copilot Premium Requests Usage Analyzer&lt;/li&gt;
  &lt;li&gt;Upload the CSV from the enterprise export (Billing and Licenses –&amp;gt; Usage –&amp;gt; Export dropdown right top)
Result can be seen here:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/images/2025/20250617/20250617_01.png&quot; alt=&quot;Screenshot of top bar information&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2025/20250617/20250617_02.png&quot; alt=&quot;Screenshot of usage statistics over time&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2025/20250617/20250617_03.png&quot; alt=&quot;Screenshot of model usage in bars&quot; /&gt;&lt;/p&gt;

&lt;p&gt;See the repo in action here (click on the link on the right side to use your own data): &lt;a href=&quot;https://github.com/devops-actions/github-copilot-premium-reqs-usage&quot;&gt;https://github.com/devops-actions/github-copilot-premium-reqs-usage&lt;/a&gt;. It’s open source, so feel free to contribute or request features!&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>GitHub Copilot &amp; Productivity</title>
			<link href="https://devopsjournal.io/blog/2025/06/07/Copilot-and-productivity"/>
			<updated>2025-06-07T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2025/06/07/Copilot-and-productivity</id>
			<content type="html">&lt;h1 id=&quot;where-the-current-focus-on-productivity-is-wrong&quot;&gt;Where the current focus on productivity is wrong&lt;/h1&gt;
&lt;p&gt;The focus on having more &lt;strong&gt;productive&lt;/strong&gt; engineers is not the right way to go. I see companies struggle all the time defining what productivity even is, and then they still tend to focus on lines of code accepted as a sense of productivity. Us engineers are busy all day with tasks like requirements engineering, architectural work, documentation, and discussions on a way forward. We think about maintainability, readability, and testability of the code we write. And then in most companies we also have all the extra things next to producing code like daily meetings, stand ups, check-ins, and so on. We hope that we get enough time to actually produce some value for our end users! I even met an engineer that starts their day later on purpose, so that they can continue on working when everyone has already left for the day: that is the time they can actually focus on writing code!&lt;/p&gt;

&lt;p&gt;We have learned several times over the last decades that just looking at things like lines of code produced is not even close to a good metric to measure productivity. And even worse: the Copilot metrics API only shows the lines of code accepted for suggestions that where accepted as a whole! So if Copilot suggests 10 lines of code and you only accept 5 (or a couple of words), the API will count this as 0 lines of code accepted. So partial acceptance is not even counted in the metrics.&lt;/p&gt;

&lt;p&gt;Still, companies want to look at lines of code suggested and accepted, and I even have seen companies that want to focus on driving that acceptance rate up! I think that is the wrong way to go, as it will lead to engineers blindly accepting suggestions without thinking about the code that is being suggested. Looking at a suggestion and thinking if this is the thing you want to do or not, or maybe change the direction you’re going in, is a good thing! GitHub is already showing information in their API to indicate the &lt;strong&gt;amount&lt;/strong&gt; of suggestions that where shown, and the &lt;strong&gt;amount&lt;/strong&gt; of suggestions that where accepted (including partial acceptances). This is already a step forward, and helps you to focus on &lt;em&gt;engagement&lt;/em&gt; instead of &lt;em&gt;productivity&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2025/20250401/20250607-Confusion.png&quot; alt=&quot;Picture of a confused engineer scratching their head surrounded with question marks&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;what-to-focus-on-instead&quot;&gt;What to focus on instead&lt;/h1&gt;
&lt;p&gt;I recommend that people instead focus on other things instead of lines of code to see if the use of GitHub Copilot has an impact. This includes looking at the entire Software Development Life Cycle (SDLC):&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;first of all you have to look at engagement and type of engagement of the engineers. Are they actually using the tool, and what kind of features are they using? I keep on running into engineers that only use the inline suggestions, and they’re not using the Chat feature at all! I find myself living in Agent Mode all day long, as I tend to know how to prompt for the right things to get the right changes, instead of building everything myself. I am thinking in &lt;strong&gt;business value&lt;/strong&gt;, instead of code.&lt;/li&gt;
  &lt;li&gt;also look at &lt;strong&gt;when&lt;/strong&gt; they use the tool. We keep on thinking that engineers work 8 hours a day, 5 days a week (depending on their schedule), but in reality they are only productive for a couple of hours a day. Sometimes they even struggle to have 2 hours a day of uninterrupted time to work on their tasks! Seriously, ask you engineers how much time they have to work on their tasks. I see team mates with overloaded agendas, having multiple meetings scheduled in parallel all day long. No wonder they cannot focus on their tasks! This fact alone amazes me every time, as businesses are still wanting to define and show the Return on Investment (ROI) of spending time on a tool like GitHub Copilot. The normal Business license is 19$ a month, and the Enterprise license is 49$ a month. If you calculate that you need to give the engineer an improvement and let them save time for around 1 hour a month to break even, you can see that the ROI is easily achieved. Of course this does not include the initial training time (there is quire a learning curve in my opinion!), but still overall, the ROI is easily achieved.&lt;/li&gt;
  &lt;li&gt;next is taking a look at the downstream changes in the SDLC. Are there more Pull Request created then before? Are the PR’s smaller or larger in size then before? Are there more or less build failures (indicating that users are testing in the pipeline, instead of testing locally)? Do you find more or less bugs in production? Are there more or less incidents in production? All these things indicate what the effects are of using GitHub Copilot in your organization.&lt;/li&gt;
  &lt;li&gt;we also keep forgetting that having these tools available for your engineers is almost expected these days. It is becoming a normal thing, just like having a decent environment available, together with other tools like a good IDE, a good version control system, and a good CI/CD pipeline. If you do not have these tools available, you will have a hard time attracting new engineers to your organization, and even worse, the existing talent might leave your company because of it. And you can better believe that your competitors are already offering these tools to their engineers! So can you even afford to be falling behind in this area?&lt;/li&gt;
  &lt;li&gt;next up is that we are supposed to work faster on the &lt;strong&gt;boring parts&lt;/strong&gt; of our jobs, where we can implement default things like boilerplate code easier and faster. The goal here is not to be more &lt;em&gt;productive&lt;/em&gt; in my opinion, but to get your valuable time back to work on the more interesting parts of your job! Use the time you save to work on the things that are more interesting to you, or the things you usually do not have time for. I recommend creating initiatives especially during the training phase of using Copilot to focus on those tasks that you never seem to have time for. Let Copilot help in the next two sprints with having better test coverage. Ask Copilot to locate places that can be refactored into more maintainable code, or more readable code. You will be amazed on the things it might find for you. I dare you to use Agent Mode for example and ask it for find all your TODO’s in the codebase and address them one by one (or create Issues from them by using the GitHub MCP server). Triage the low hanging fruit with Copilot and ask it for a fix. Don’t have a linter in the project yet? Add it and ask Copilot to fix all the linting issues it finds. Keep forgetting to update your dependencies? Ask Copilot for a Dependabot config for all the ecosystems you have in your repo. Always wanted to run your linting/unit tests in a CI (Continuous Integration) pipeline and never got to it? Guess what… Copilot can help here! The possibilities are endless, and you can use Copilot to help you with the boring parts of your job.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;whats-next&quot;&gt;What’s next?&lt;/h1&gt;
&lt;p&gt;I jotted down how I think we need to change the way of thinking around productivity in this blogpost. Then we are liberated to start thinking about the way GitHub Copilot changes the way we produce our applications and bring actual value to our end users. And even better, how we can really level up and also enable our non-engineering team members to work more efficiently as well. I have written more about this in this blogpost &lt;a href=&quot;/blog/2025/04/01/GitHub-Copilot-Change-the-Narrative&quot;&gt;GitHub Copilot - Change the Narrative&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Lets work in the right direction and embrace the future state of working by leveraging Generative AI more and more, and leave the past behind us. I think we will become more of an orchestrator of AI agents, and the agents can only go faster if we understand how to build a solid foundation to trust on (see that blogpost).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2025/20250401/20250401-Value.png&quot; alt=&quot;Image of adding value to the end user&quot; /&gt;&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>GitHub Copilot - Change the Narrative</title>
			<link href="https://devopsjournal.io/blog/2025/04/01/GitHub-Copilot-Change-the-Narrative"/>
			<updated>2025-04-01T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2025/04/01/GitHub-Copilot-Change-the-Narrative</id>
			<content type="html">&lt;p&gt;TL;DR:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Changing the narrative on GitHub Copilot from focus on engineers and productivity to focus on a sturdy (DevOps) foundation to be able to go faster.&lt;/li&gt;
  &lt;li&gt;Next frontier: the rest of our organization&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;premise-current-narrative-is-not-helping&quot;&gt;Premise: current narrative is not helping&lt;/h1&gt;
&lt;p&gt;In my opinion we need to shift the narrative on enabling engineers to use GitHub Copilot. Currently there is a lot of focus on the engineers that can produce code easier and faster using GitHub Copilot. That leads to companies thinking they finally found a way to create 10x engineers. And even engineers are hyping this up with stories around “vibe coding” with AI: they jump on their keyboards with a prompt and accept every suggestion that is there and then run the application to figure out if their initial problem was solved or not. Eventually this path leads to disappointment: either the code does not work as hoped, or there was crucial information missing and the AI took a wrong turn somewhere. Even worse: we have seen Generative AI following the “scouting rule” where it starts to clean up after itself, changing code that did not need to be changed at all! This can lead to impact in other places in the codebase that can introduce new bugs. Even worse with all the “vibe coding” stories, we see engineers that are not even testing their code before pushing it to production. This is a recipe for disaster and will lead to a lot of frustration and disappointment in the long run.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2025/20250401/20250607-Confusion.png&quot; alt=&quot;Picture of a confused engineer scratching their head surrounded with question marks&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We also tend to focus on the wrong metrics to see if the use of GitHub Copilot has an impact. I explained more in my follow up blogpost &lt;a href=&quot;/blog/2025/06/07/Copilot-and-productivity&quot;&gt;GitHub Copilot &amp;amp; Productivity&lt;/a&gt; on this topic. Focusing just on productivity is not the right way to go in my opinion. Let alone that we are not able to define what that even is.&lt;/p&gt;

&lt;h1 id=&quot;better-narrative-focus-on-having-a-sturdy-devops-foundation&quot;&gt;Better narrative: focus on having a sturdy (DevOps) foundation&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;/images/2025/20250401/20250401-DevOpsFoundation.png&quot; alt=&quot;Picture of a sturdy DevOps foundation to support the engineer&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Instead I recommend to focus on enabling engineers to work on laying the &lt;strong&gt;foundation&lt;/strong&gt; to be able to roll out their applications faster and with more &lt;strong&gt;trust&lt;/strong&gt; that their application works as intended. I’ve always been a DevOps person and I firmly believe in having the basic principles in place:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Automated pipelines and testing&lt;/li&gt;
  &lt;li&gt;Everything as code or configuration&lt;/li&gt;
  &lt;li&gt;More eyes principle in place&lt;/li&gt;
  &lt;li&gt;Enough testing in place to have trust
If a deployment fails for any reason, a new test should be added to the pipeline to prevent it from happening again.&lt;/li&gt;
  &lt;li&gt;Continuous monitoring and feedback loops&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only when a large portion of these fundamentals are in place, can a team of engineers roll out their changes faster, as for example the testing is in place to be able to rely on their deployments. Note that this can be achieved in multiple ways, for example with unit, regression, or integration testing. Use what works for your application.&lt;/p&gt;

&lt;p&gt;Generative AI like GitHub Copilot can help to put these foundations in place and works really well for those kind of supporting system. I see a lot of users only focus on coding with Copilot, not knowing it can do so much more: I’ve created all sorts of scripts, pipelines, and even Splunk queries with Copilot and it works really well if you understand how to prompt for the right things.&lt;/p&gt;

&lt;p&gt;I see GitHub Copilot as an enabler to give teams the time to get their foundation in order. Since the normal tasks are sped up, the extra time can be spent on initiatives to improve the things that often get pushed to the bottom of the backlog, or at least pushed out of the sprint. I recommend teams always embed these types of technical debt and either include it in their way of working, or specially carve out time in every sprint to improve it. I’ve seen successful teams always having a focus on 10% of their time on technical debt fixing as part of their sprint.&lt;/p&gt;

&lt;p&gt;With the foundation in place teams can actually deliver value faster, with increased trust, and with fewer issues in production.&lt;/p&gt;

&lt;h1 id=&quot;next-level-of-enlightenment-team-efforts&quot;&gt;Next level of enlightenment: team efforts&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;/images/2025/20250401/20250401-Value.png&quot; alt=&quot;Image of adding value to the end user&quot; /&gt;&lt;/p&gt;

&lt;p&gt;After that we can actually start to think at the other things our teams do. On average an engineer is already lucky if they can focus on writing some code for a period of around two hours a day: &lt;a href=&quot;https://www.activestate.com/wp-content/uploads/2019/05/ActiveState-Developer-Survey-2019-Open-Source-Runtime-Pains.pdf&quot;&gt;ActiveState’s 2019 Developer Survey&lt;/a&gt;. The rest of the time is spend on preparations, discussions, architectural work, documentation, etc. And then there are always teams that spend most of their day in meetings, sometimes even overlapping and in parallel during their day. It’s always amazing to see how some organizations have made meeting more important then actual creative work like coding!&lt;/p&gt;

&lt;p&gt;The next shift that I see happening is to the way work flows to engineers. We’re constantly busy with describing the changes we want to make to the application, and have to translate user requests into actionable descriptions. From that we need to make sure all team members understand these changes as well as the impact on the application. Teams are working with different stakeholders and perhaps a product owner that funnels the work to the team.&lt;/p&gt;

&lt;p&gt;In my opinion we now need to focus on enabling roles like product owners to send in better scoped work. Then review the incoming work descriptions by an engineer that has expertise on the application to add the finishing touches.&lt;/p&gt;

&lt;p&gt;When the incoming work has enough clarity AI can be used to suggest the changes translated into code changes. The next iteration of tools like GitHub Copilot has already been announced with project Padawan (blogpost &lt;a href=&quot;https://github.blog/news-insights/product-news/github-copilot-the-agent-awakens/&quot;&gt;here&lt;/a&gt;), where Copilot can try to suggest the changes on its own, rerun all the tests in the pipeline, and reiterate if needed. When the tests succeed and the changes have been implemented, it will submit a pull request for final review.&lt;/p&gt;

&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;There will always be work for highly skilled engineers, and yes, I think they will become more of an orchestrator in the future of AI agents that can produce large parts of the work. The engineers will be in charge making sure there is enough trust in the system to proceed with the next steps. It is crucial to then have a good way in place to train new engineers to be proficient in the tools and processes that are in place and understand the impact that changes will have onto the applications they work on. Ignoring to train new engineers will lead to a lot more (downstream) issues and frustration by both the engineers and their stakeholders. Embracing the new way of working will lead to a more efficient way of working, and ultimately to a better experience for the end users of the applications we build.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Really keeping your GitHub Actions usage secure</title>
			<link href="https://devopsjournal.io/blog/2025/03/16/Really-keepingyour-GitHub-Actions-usage-secure"/>
			<updated>2025-03-16T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2025/03/16/Really%20keepingyour-GitHub-Actions-usage-secure</id>
			<content type="html">&lt;p&gt;Last Friday what we expected happened: a much used GitHub Action got compromised, read all about it here at the StepSecurity blog where they explain that they detected the issue and jumped into action: &lt;a href=&quot;https://www.stepsecurity.io/blog/harden-runner-detection-tj-actions-changed-files-action-is-compromised&quot;&gt;Step Security Blog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2025/20250316/20250316-SplashImage.jpg&quot; alt=&quot;Photo of a person in a yellow raincoat overlooking a horseshoe bent in a road, watching from above&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;so-what-happened&quot;&gt;So what happened?&lt;/h2&gt;
&lt;p&gt;What happened? An Action that was used by over 23.000 public repositories (and who knows how many more private repositories) suffered a compromise where an attacker added malicious code to leak out repository secrets. They also updated all version tags to the new commit, so that anyone who is not following the best practices around GitHub Actions was now leaking out sensitive data, out into the open for public repos (as their Action logs are publicly available as well). The CVE (Common Vulnerability Enumerator) logged for this incident can be found &lt;a href=&quot;https://nvd.nist.gov/vuln/detail/cve-2025-30066&quot;&gt;here as CVE-2025-30066&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Of course this incident happened on a Friday, just before most people log off for the weekend. There is a good chance your workflow did run in a compromised state, so check your logs!&lt;/p&gt;

&lt;h2 id=&quot;how-to-fix-this&quot;&gt;How to fix this?&lt;/h2&gt;
&lt;p&gt;StepSecurity stepped into action and published a version of the action that was not compromised in their own Actions organization: &lt;a href=&quot;https://github.com/step-security/changed-files&quot;&gt;https://github.com/step-security/changed-files&lt;/a&gt;. They create point in time backups for several actions so that their customers can rely on secured and locked down actions, as they need to follow an approval and validation process before the copy of the action is updated. This process helps finding compromises in updates of the action, and keeps the end user safer.&lt;/p&gt;

&lt;p&gt;In the mean time the repository has been purged of the malicious code and is back online. Good change a lot of people did not even notice their repository was leaking out information!&lt;/p&gt;

&lt;h2 id=&quot;fixing-is-nice-but-only-if-you-were-aware-at-all&quot;&gt;Fixing is nice, but only if you were aware at all&lt;/h2&gt;
&lt;p&gt;Having a backup of the action for people to switch to can be very helpful, especially since the original GitHub repository got removed leading to errors when the workflows reliant on the action started to run during this period. Knowing about the actual backup is of course then a different challenge. The news about the attack did bubble up through the GitHub Advisories Database, but you will only get a notification if you actual have Dependency Security Alerts enabled on your repository (I have a &lt;a href=&quot;https://www.linkedin.com/learning/github-advanced-security-ghas/vulnerable-alerts-management?resume=false&quot;&gt;video&lt;/a&gt; on that on LinkedIn Learning).&lt;/p&gt;

&lt;h2 id=&quot;how-do-i-prevent-this-from-happening-to-me&quot;&gt;How do I prevent this from happening to me?&lt;/h2&gt;
&lt;p&gt;Al this leads me to the information I wanted to share here. Given the way the GitHub Actions ecosystem works, I bet a lot of people will not be aware of what happened during this attack. Having Dependency Security Alerts on is a good way to at least get a notification, but do realize this is happening after the fact and your workflows might already have been compromised.&lt;/p&gt;

&lt;p&gt;There are two ways to really protect yourself from these types of attacks:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Use a service from a company like StepSecurity that will validate, secure and store backups of the actions you use. This will prevent downtime in the case that the action gets removed, like it temporarily was in this case.&lt;/li&gt;
  &lt;li&gt;Setup your own backups of the Actions you (or you company) uses. Doing so gives you a point in time backup of the action code (so you can still run your workflow). An additional benefit is that you can control when the backup gets updated, and add extra reviews and security checks to it!&lt;/li&gt;
  &lt;li&gt;Next to that it is super important to use tools to know what dependencies you have in your supply chain. You should have GitHub’s Dependency Graph and Security Alerts enabled on everything (available for free for public repos). The paid version also gives you dashboards with an overview of your entire organization / enterprise.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also run these inventories yourself, but using for example the &lt;a href=&quot;https://github.com/devops-actions/load-used-actions&quot;&gt;devops-actions/load-used-actions&lt;/a&gt; that I maintain.&lt;/p&gt;

&lt;h2 id=&quot;setting-up-a-your-own-backup-process&quot;&gt;Setting up a your own backup process&lt;/h2&gt;
&lt;p&gt;Ever since GitHub Actions became available, I have been advocating to take control over the actions you rely on. My earliest blogposts around this topic are from 2021!&lt;/p&gt;

&lt;p&gt;This process comes with a couple of steps:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Fork actions to an organization you own. I recommend a completely separate organization so it is clear what information is stored in that location.
Take ownership over the updates of the actions by using the GitHub Fork Updater. This will give you a process that allows you to review every single update, before you can use the update in your workflows. You can then add extra security checks in that process as well.&lt;/li&gt;
  &lt;li&gt;This will lead to setting up an &lt;a href=&quot;blog/2021/10/14/GitHub-Actions-Internal-Marketplace&quot;&gt;internal marketplace&lt;/a&gt; for your organization to use. This will allow you to take inventory over you used actions (and for example easily show you in what workflows a certain actions is used). Additionally you can then setup a request process for actions to be added, so that you can even run all your required security checks up front.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Going through these steps is unfortunately something that every user and company needs to go through to secure their pipelines, which is a key action to take when looking at supply chain security with a security framework like &lt;a href=&quot;https://slsa.dev&quot;&gt;slsa.dev&lt;/a&gt;. And do not think your security responsibility stops with taking ownership over the actions repo! When the action pulls in dependencies, you need to secure those dependencies as well! I have seen actions that download binaries or shell scripts on execution and start running them. Or what about docker images that get downloaded just in time?&lt;/p&gt;

&lt;h2 id=&quot;next-steps&quot;&gt;Next steps&lt;/h2&gt;
&lt;p&gt;Want to know more? I have presented a session on Using GitHub Actions with Security in Mind several times. A recording from GitHub Universe 2021 can be found &lt;a href=&quot;https://www.youtube.com/watch?v=Ers-LcA7Nmc&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I have also co-authored a book on GitHub Actions that also includes these security aspects. You can get that book on &lt;a href=&quot;https://www.manning.com/books/github-actions-in-action&quot;&gt;Manning.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you want to deep dive into the security tools GitHub has available, then find my course on &lt;a href=&quot;https://www.linkedin.com/learning/github-advanced-security&quot;&gt;GitHub Advanced Security&lt;/a&gt; on LinkedIn Learning.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>DevCon Romania 2024 - Protect yourself against supply chain attacks</title>
			<link href="https://devopsjournal.io/blog/2024/11/07/DevCon-Romania-2024-Protect-yourself-against-supply-chain-attacks"/>
			<updated>2024-11-07T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2024/11/07/DevCon-Romania-2024-Protect-yourself-against-supply-chain-attacks</id>
			<content type="html">&lt;p&gt;Today I shared my story on why and how we can protect ourselves against supply chain attacks. This talk was part of the DevCon Romania 2024 conference on the DevOps track.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2024/20241107/20241107_photo1.jpg&quot; alt=&quot;Photo of Rob on the stage&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I find it important to share with folks how to think about our supply chains in the software we deliver, from dependencies we deploy to production, to everything that touches the code &lt;strong&gt;before&lt;/strong&gt; it gets into the production environment. There are so many attack vectors in our pipelines that we need to be aware of and protect against.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2024/20241107/20241107_photo2.jpg&quot; alt=&quot;Photo of Rob on the stage, showing the full room&quot; /&gt;&lt;br /&gt;
We had a nice full room with lots of interested engineers and developers. I tried to inspire them to take a closer look at their pipelines and see where they can improve their security posture.&lt;/p&gt;

&lt;p&gt;You can find all of my slides in the PDF here to look at all the links I shared to level up!&lt;/p&gt;

&lt;p&gt;Here is the &lt;a href=&quot;/slides/20241107_DevCon.ro-Protect-yourself-against-supply-chain-attacks.pdf&quot;&gt;link to pdf of the presentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Feel free to connect with me on &lt;a href=&quot;https://linkedin.com/in/bosrob&quot;&gt;LinkedIn&lt;/a&gt; and ask me questions about this talk or anything else you want to know about!&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>GitHub Universe 2024 - Successfully scaling GitHub Copilot to thousands of developers</title>
			<link href="https://devopsjournal.io/blog/2024/10/30/GitHub-Universe-slides"/>
			<updated>2024-10-30T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2024/10/30/GitHub-Universe-slides</id>
			<content type="html">&lt;p&gt;This year I got to share my story on how I think you can successfully scale GitHub Copilot to thousands of developers. This talk was part of the GitHub Universe 2024 conference in San Francisco, one of my favorite conferences to go to every year. The vibe of the GitHub community is always so welcoming and inspiring! Every one is very open and approachable, and you never know who you might bump into!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2024/20241030/20241029_150537380_iOS.jpg&quot; alt=&quot;Photo of the Xebia group at the standup on our stand&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The session has been recorded and can be watched with the viewer below:&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/eJRNJwlLFts?si=n-4eLbCNaLsP3zDC&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;You can find the slides of my session &lt;a href=&quot;/slides/20241030_AI1197B-Successfully-scaling-GitHub-Copilot-to-thousands-of-developers.pdf&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Feel free to connect with me on &lt;a href=&quot;https://linkedin.com/in/bosrob&quot;&gt;LinkedIn&lt;/a&gt; and ask me questions about this talk or anything else you want to know about!&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>GitHub Models and Inference API</title>
			<link href="https://devopsjournal.io/blog/2024/09/29/GitHub-Models-API"/>
			<updated>2024-09-29T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2024/09/29/GitHub-Models-API</id>
			<content type="html">&lt;p&gt;Need to use the Azure Inference AI SDK in Python against Azure OpenAI? Then this tip is for you! I ran into an issue converting the default examples to not run against GitHub’s Model endpoint but against an Azure OpenAI endpoint. 
The code example below says it all: configure your credential the correct way to get this to work.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;os&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;azure.ai.inference&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ChatCompletionsClient&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;azure.ai.inference.models&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SystemMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;UserMessage&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;azure.core.credentials&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AzureKeyCredential&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Set the runtime to &quot;GITHUB&quot; if you are running this code in GitHub 
# or something else to hit your own Azure OpenAI endpoint
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;runtime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;AZURE&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;runtime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;GITHUB&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Running in GitHub&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;token&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;environ&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;GITHUB_TOKEN&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ENDPOINT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;https://models.inference.ai.azure.com&quot;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ChatCompletionsClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;endpoint&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ENDPOINT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;credential&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AzureKeyCredential&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Running in Azure&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;token&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;environ&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;AI_TOKEN&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ENDPOINT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;https://xms-openai.openai.azure.com/openai/deployments/gpt-4o&quot;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ChatCompletionsClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;endpoint&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ENDPOINT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;credential&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AzureKeyCredential&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# Pass in an empty value here!
&lt;/span&gt;        &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;api-key&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Include your token here
&lt;/span&gt;        &lt;span class=&quot;c1&quot;&gt;#api_version=&quot;2024-06-01&quot;  # AOAI api-version is not required
&lt;/span&gt;    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
		</entry>
	
		<entry>
			<title>GitHub Copilot Extensions</title>
			<link href="https://devopsjournal.io/blog/2024/09/14/GitHub-Copilot-Extensions"/>
			<updated>2024-09-14T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2024/09/14/GitHub-Copilot-Extensions</id>
			<content type="html">&lt;p&gt;GitHub Copilot is a great tool to help you write code. The next phase is starting now by enabling you to write your own extensions for Copilot! This is a great way to extend the capabilities of Copilot to your own needs. You can for example look in your own knowledge store for information, or even call into an API to get the information you need. All from within GitHub Copilot Chat itself!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2024/20240914/Announcement.png&quot; alt=&quot;Announcement image&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Some examples of this way of working with Copilot are:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;@workspace/#solution - this works inside of your IDE&lt;/li&gt;
  &lt;li&gt;@github - this works with the GitHub API and you can ask questions like “get all issues for this repo” or “get all PRs for this repo”. Note, only available for GitHub Copilot Enterprise.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next to that we are seeing some extensions coming out. There are two types of extensions you can build at the moment;&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;VS Code extensions for Copilot: this is a way to extend the capabilities of Copilot in your IDE and it has access to information in your IDE, like the files you have open. Do note that this extension will only work in VS Code, and in none of the other supported IDE’s for Copilot. An example of this type of extension is the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@azure&lt;/code&gt; extension (see &lt;a href=&quot;https://techcommunity.microsoft.com/t5/microsoft-developer-community/introducing-github-copilot-for-azure-your-cloud-coding-companion/ba-p/4127644&quot;&gt;announcement&lt;/a&gt;), that allows you to interact with an Azure knowledge base (currently in private preview). Learn from &lt;a href=&quot;https://github.com/molson504x/copilot-custom-extension&quot;&gt;Matt Olson’s post&lt;/a&gt; how to build one of these.&lt;/li&gt;
  &lt;li&gt;GitHub Copilot Extensions: these live somewhere online (where ever you decide to host them) and can be called from within Copilot Chat in any UI that supports it (currently only VS Code and the GitHub web interface on github.com). That is what this post is on!&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;writing-a-github-copilot-extension&quot;&gt;Writing a GitHub Copilot Extension&lt;/h2&gt;

&lt;p&gt;Get started to write your own Copilot extension by reading the &lt;a href=&quot;https://github.blog/news-insights/product-news/introducing-github-copilot-extensions/&quot;&gt;announcement&lt;/a&gt; and the &lt;a href=&quot;https://docs.github.com/en/copilot/building-copilot-extensions/about-building-copilot-extensions&quot;&gt;documentation&lt;/a&gt;. From that you can create a GitHub App (public or private to your organization) and go to the Copilot part of the settings (see next screenshot).&lt;/p&gt;

&lt;p&gt;After creating the GitHub App, you need to configure the App with a &lt;em&gt;public&lt;/em&gt; endpoint that will be called by Copilot. This endpoint should be able to handle the requests from Copilot and return the results in the correct format. The easies way to get started is to create a &lt;a href=&quot;https://github.com/features/codespaces&quot;&gt;Codespace&lt;/a&gt; and use the Codespace URL when you run your solution as the endpoint for your extension:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2024/20240914/app%20settings.png&quot; alt=&quot;Screenshot of the Codespace URL GitHub App&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The url in the screenshot is the same as the one I am using in the ‘Callback URL’ setting on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;General&lt;/code&gt; tab of the GitHub App settings.&lt;/p&gt;

&lt;p&gt;There are a lot of code examples in the &lt;a href=&quot;https://github.com/copilot-extensions&quot;&gt;copilot-extensions&lt;/a&gt; organization, like the following:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Blackbeard extension (talk like a pirate)&lt;/li&gt;
  &lt;li&gt;RAG extension (implement ‘retrieval augmented generation’ to build up your response in several steps)&lt;/li&gt;
  &lt;li&gt;Function calling extension (retrieve a function from the user prompt, like “what is the weather in Amsterdam”)&lt;/li&gt;
  &lt;li&gt;GitHub Models extension (use the new models API’s to talk to an LLM)
These are fine to spelunk in, but as soon as you want to do something else then adding a system prompt and stream back the LLM response, you will find some very rough edges.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead I recommend to go straight for the &lt;a href=&quot;https://github.com/copilot-extensions/preview-sdk.js&quot;&gt;Copilot Extension SDK&lt;/a&gt; library. At the time of writing this SDK in in early alpha stage, but it solves several issues that you will run into when you start building your own extension. I’ve been adding some examples to that repository to help you get started.&lt;/p&gt;

&lt;h3 id=&quot;writing-the-extension-code&quot;&gt;Writing the extension code&lt;/h3&gt;
&lt;p&gt;Using the examples in the SDK repo should get you started. The main things you need are in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;README.md&lt;/code&gt; in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;examples&lt;/code&gt; folder. Run the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm install&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm run watch&lt;/code&gt; commands to get the examples running. Don’t forget to make your Codespace port public (so that Copilot can send its messages to it), and to configure the Codespace port URL in the GitHub App settings.&lt;/p&gt;

&lt;p&gt;You need to handle a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;POST&lt;/code&gt; event into your application, and then return text and a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;createDoneEvent&lt;/code&gt; to indicate your response is complete.&lt;/p&gt;

&lt;p&gt;If you want to, you can create a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;createAckEvent&lt;/code&gt; that will tell Copilot you have received the request and are working on it. It will display that status to the user and is a nice way to let the user know you are working on their request, especially when you will be going out to an LLM to generate some text for example.&lt;/p&gt;

&lt;p&gt;After the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;acknowledge&lt;/code&gt; event, you can add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;createTextEvent(&quot;your text here&quot;)&lt;/code&gt; to add custom text to the output. What you do next is up to you. There are several options already supported in the SDK:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Call the GitHub Copilot API with the user prompt and get the response back (limited model options)&lt;/li&gt;
  &lt;li&gt;Call into an API you have access to&lt;/li&gt;
  &lt;li&gt;Ask the user to confirm something (example later in this post)&lt;/li&gt;
  &lt;li&gt;Call into GitHub Models to get a response from an LLM with more model options&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;running-the-extension-server-side-in-a-codespace&quot;&gt;Running the extension server side in a Codespace&lt;/h2&gt;
&lt;p&gt;Working on the extension inside of a &lt;a href=&quot;https://github.com/features/codespaces&quot;&gt;Codespace&lt;/a&gt; is the most easy way to develop an extension: you receive the traffic directly and you can debug all you want. Do note that I’d recommend using a separate GitHub App for this compared to your production version, as ALL the traffic from the GitHub App will be sent your way, which can be confusing to determine what is coming from.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2024/20240914/Running-the-extension.png&quot; alt=&quot;Screenshot of the running the extension inside of a Codespace&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Don’t forget to make the port public in the Codespace settings, and to configure the URL in the GitHub App settings + the Copilot settings section of the GitHub App. If you do not have a Copilot section in the App, then your App is not flagged in for Copilot extensions.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2024/20240914/AppSettings.png&quot; alt=&quot;Screenshot of the Codespace settings&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;working-with-the-extension&quot;&gt;Working with the extension&lt;/h2&gt;
&lt;p&gt;As soon as you install the GitHub App in an organization (has to have Copilot licenses enabled), every user in that organization can ask it questions. Do note that GitHub Copilot always checks the incoming prompt for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@&lt;/code&gt; symbol, so you need to start your prompt with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@your-app-name&lt;/code&gt; to trigger the extension. Once you have done so, the entire conversation will be send to your extension, as there is only one extension per thread that can be interacted with. This is probably for security reasons I think, as the extension will get the entire conversation and can do with it what it wants. You can imagine if your extension would be able to read the conversation from another app, sensitive information could be leaked.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2024/20240914/01-Invoking-the-extension.png&quot; alt=&quot;Screenshot of invoking the extension by calling @xebia with your prompt. It shows the result of the prompt that indicates it has been written with the &apos;talk like a pirate&apos; system prompt&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You can now try out the extension in every place that supports it! At the time of writing this post, it is available in VS Code (+ Insiders) and the Web UI on github.com. You can ask the extension for something and it will send the prompt to your extension. Do note that GitHub Copilot by itself checks the incoming prompt with its content filter, to prevent harmful messages from being answered. Only when you pass the filter (like for example it needs to be a coding related prompt), the extension will be called. Same goes for the data you send back, it will be checked by GitHub Copilot before it is shown to the user. I first started with just “hi matey” as a response, but that was blocked and thus not shown to the user.&lt;/p&gt;

&lt;p&gt;When you first interact with a GitHub Copilot Extension, you get a message asking for consent to share the conversation with the extension. Likewise the extension will get your user information (GitHub handle, Location (approximate)). This is to be able to personalize the response to the user. You can always revoke this consent in the &lt;a href=&quot;https://github.com/settings/installations&quot;&gt;GitHub settings&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2024/20240914/Allow-prompt.png&quot; alt=&quot;Screenshot asking for Allow access to the new Copilot extension&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;adding-system-prompts&quot;&gt;Adding system prompts&lt;/h2&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;prompt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;createAckEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;createDoneEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;createTextEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;parseRequestBody&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;@copilot-extensions/preview-sdk&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;tokenForUser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;x-github-token&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;payload&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;parseRequestBody&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;prompt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;payload_message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;messages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;messages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// add the current message thread in here for the LLM to use&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;tokenForUser&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createTextEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createDoneEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Response sent&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;working-with-user-confirmation&quot;&gt;Working with User Confirmation&lt;/h2&gt;
&lt;p&gt;You also have the option to work with user confirmation in your extension. Copilot Extensions are really shaping up to give you endless possibilities like making it an ‘agent’ that can do things for you. Asking a question about a repository and an API that does not exist? Let your Copilot Extension fill an issue in a repo with the question and the answer from the API. Or ask the user to confirm something, like in the following example:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getUserConfirmation&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;@copilot-extensions/preview-sdk&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;createConfirmationEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;123&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Are you sure?&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Create an issue with the missing API surface.&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}),&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/images/2024/20240914/PromptAccept.png&quot; alt=&quot;Screenshot showing the user confirm dialog with accept and dismiss buttons&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;GitHub Copilot Extensions are a great way to extend the capabilities of Copilot to your own needs. You can for example look in your own knowledge store for information (API, Database, an LLM with your own data). All from within GitHub Copilot Chat itself! The possibilities are endless and I am looking forward to see what you will build with it.&lt;/p&gt;

&lt;p&gt;I am planning to integrate this with our internal knowledge base, starting with something simple like “What is the GitHub handle for Rob Bos?” and then expanding from there. I am also looking forward to see what other people will build with this, as the possibilities are endless.&lt;/p&gt;

&lt;p&gt;What ideas do you have to build with this? Let me know in the comments below!&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>GitHub Copilot Chat - Power User example</title>
			<link href="https://devopsjournal.io/blog/2024/06/19/GitHub-Copilot-Chat-Power-User"/>
			<updated>2024-06-19T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2024/06/19/GitHub-Copilot-Chat-Power-User</id>
			<content type="html">&lt;p&gt;Learn how I use GitHub Copilot Chat to my benefit and see the end to end flow on creating a script to call into the GitHub API.&lt;/p&gt;

&lt;p&gt;I’m showing my prompts and the results, as well where Copilot failed to help me in one go. With spening more time on my propmt up front, I managed to get a much better result that with the first attempt!&lt;/p&gt;

&lt;h1 id=&quot;video&quot;&gt;Video&lt;/h1&gt;

&lt;p&gt;In this video I show how I use the chat feature to let Copilot write an entire script for me in the form of calling an API (in a loop on a certain input file), summarize the results based on the response, and calculate both an average inside the loop (per item) as well as an overall average.&lt;/p&gt;

&lt;iframe width=&quot;1183&quot; height=&quot;665&quot; src=&quot;https://www.youtube.com/embed/P3Q5wa0mI_0&quot; title=&quot;GitHub Copilot Chat - Power user example&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
</content>
		</entry>
	
		<entry>
			<title>GitHub Copilot - Levels of enlightenment</title>
			<link href="https://devopsjournal.io/blog/2024/06/07/GitHub-Copilot-Levels-of-enlightenment"/>
			<updated>2024-06-07T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2024/06/07/GitHub-Copilot-Levels-of-enlightenment</id>
			<content type="html">&lt;p&gt;I’ve recorded a video series on the lessons I learned to get the most out of GitHub Copilot. This series is called “Levels of Enlightenment” and can be found in this &lt;a href=&quot;https://www.youtube.com/playlist?list=PLXVVwOM8uv2y0Yo6H8qu9giWWWlZLzu8K&quot;&gt;YouTube Playlist&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;An overview of these level of enlightenment can be seen in this image:&lt;br /&gt;
&lt;img src=&quot;/images/2024/20240607/20240607_1_Overview.png&quot; alt=&quot;Overview of all videos in one image, listing out the video names&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;overview&quot;&gt;Overview&lt;/h1&gt;
&lt;p&gt;Learn about my journey using GitHub Copilot over the last year and a half. Each video in this series shows one of my lessons learned in those “aha” moments and how it helped me getting more value out of it.&lt;/p&gt;

&lt;p&gt;Follow along and pick something up that helps you getting to the next level of enlightenment!&lt;/p&gt;

&lt;h2 id=&quot;1-explain-what-you-want-to-achieve&quot;&gt;1. Explain what you want to achieve&lt;/h2&gt;

&lt;p&gt;In this video I show how changing the way I write my code helps me get more descriptive with GitHub Copilot so the suggestions I get out of it become a lot better! View the video on &lt;a href=&quot;https://youtu.be/vdGW48mJgUA&quot;&gt;YouTube&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;2-know-your-context&quot;&gt;2. Know your context&lt;/h2&gt;

&lt;p&gt;In this video I show you how to think about the context GitHub Copilot uses and how to use that knowledge to get better results out of it! View the video on &lt;a href=&quot;https://youtu.be/FlfgYQ9NCao&quot;&gt;YouTube&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;3-copy-method-calls-as-comments&quot;&gt;3. Copy method calls as comments&lt;/h2&gt;

&lt;p&gt;In this video I show how changing the way I go from a method call to the method implementation speeds up my way of creating code! View the video on &lt;a href=&quot;https://youtu.be/tzwT4VcXhg8&quot;&gt;YouTube&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;4-top-down-programming-instead-of-bottom-up&quot;&gt;4. Top-down programming instead of bottom-up&lt;/h2&gt;

&lt;p&gt;In this video I show how thinking about my code implementation from top-down instead of bottom up helps me use GitHub Copilot in a better way! View the video on &lt;a href=&quot;https://youtu.be/ispTBp9FfFY&quot;&gt;YouTube&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;5-typos-in-your-prompt-do-not-matter&quot;&gt;5. Typos in your prompt do not matter&lt;/h2&gt;

&lt;p&gt;In this video I show you to stop caring (to much) about typos in your prompt. Took me a while to internalize that this is a good way to speed up your prompting! View the video on &lt;a href=&quot;https://youtu.be/b_UG94dxD04&quot;&gt;YouTube&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;6-use-the-chat&quot;&gt;6. Use the chat&lt;/h2&gt;

&lt;p&gt;In this video I talk about using the Chat functionality to your benefit: instead of relying on suggestions while you are typing most of the time, I have switched to use the Chat for most of the things, as that will get me faster results so I can focus on what I want to do, instead of creating the right syntax. View the video on &lt;a href=&quot;https://youtu.be/iSPN97vtNzU&quot;&gt;YouTube&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;7-file-is-your-biggest-friend&quot;&gt;7. #file is your biggest friend&lt;/h2&gt;

&lt;p&gt;In this video I show you how to get the most of of GitHub Copilot by being very descriptive with your prompts and to include specific context by using #file or #selection, instead of relying on the editor to guess what parts of your codebase are important. View the video on &lt;a href=&quot;https://youtu.be/xVsfgeB4jQI&quot;&gt;YouTube&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;8-accept-the-truth&quot;&gt;8. Accept the truth&lt;/h2&gt;

&lt;p&gt;In this video I talk about accepting the fact that Copilot is based on Large Language Models, which means it is both non-deterministic as well as not 100% correct. You remain the pilot and are driving the conversation, so you are also the professional guiding the code in a direction. It can take a while to get the code correct :smile: View the video on &lt;a href=&quot;https://youtu.be/B7suezj2c9U&quot;&gt;YouTube&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;9-be-smart&quot;&gt;9. Be smart&lt;/h2&gt;

&lt;p&gt;In this video I talk about being smart when using GitHub Copilot. Think of novel use cases and how to get more done by Copilot instead of writing the code yourself. Use it for exploration and finding new ways of doing things! View the video on &lt;a href=&quot;https://youtu.be/FdLHeCxygFQ&quot;&gt;YouTube&lt;/a&gt;.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>GitHub Copilot Power User example</title>
			<link href="https://devopsjournal.io/blog/2024/06/05/GitHub-Copilot-Power-User"/>
			<updated>2024-06-05T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2024/06/05/GitHub-Copilot-Power-User</id>
			<content type="html">&lt;p&gt;Learn how I use GitHub Copilot to my benefit and see the end to end flow on a code refactoring example where I extract a script from a yaml pipeline (GitHub Actions) into a separate file.&lt;/p&gt;

&lt;p&gt;I’m showing my prompts and the results, as well where Copilot failed to help me in one go. With a follow up question and call, I still managed to get the result that I wanted!&lt;/p&gt;

&lt;h1 id=&quot;video&quot;&gt;Video&lt;/h1&gt;

&lt;iframe width=&quot;1183&quot; height=&quot;665&quot; src=&quot;https://www.youtube.com/embed/_DEBSOQC2c0&quot; title=&quot;GitHub Copilot - Power user example&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h1 id=&quot;slides&quot;&gt;Slides&lt;/h1&gt;
&lt;p&gt;The slides for this video should be self explanatory if you prefer to read instead of watch. You can find them here: &lt;a href=&quot;/slides/20240605_GitHub_Copilot_Power_User.pdf&quot;&gt;GitHub Copilot - Power user example&lt;/a&gt;.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>GHAS Code Security Configuration</title>
			<link href="https://devopsjournal.io/blog/2024/04/27/GHAS-code-security-configuration"/>
			<updated>2024-04-27T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2024/04/27/GHAS-code-security-configuration</id>
			<content type="html">&lt;p&gt;GitHub Advanced security has gotten quite a big update in public beta at the moment that helps with the rollout of Advanced Security features across your organization. It is called “Code security configurations” and it allows you to set up a default configuration for some or all repositories in your organization.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2024/20240427/20240427_HeroImage.png&quot; alt=&quot;Image of an unlocked door in an enchanted forest. Generated with Microsoft Copilot Designer&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;previous-situation&quot;&gt;Previous situation&lt;/h1&gt;
&lt;p&gt;Up to now there where only three options during the rollout:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Enable features for new repos only (organization level setting).&lt;/li&gt;
  &lt;li&gt;Enable GHAS for ALL repos in one go (rather intrusive and needs extensive training up front of all developers).&lt;/li&gt;
  &lt;li&gt;Enable features on a per repo basis (slower and fits a team-by-team onboarding plan) .&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So this means either going slow(er) on a team-by-team basis or going fast and enabling everything at once. That last one is not really an option in my opinion, unless you want to alienate a lot of engineers from the security process. I always recommend giving people training and discuss the expectations that you have of what they will need to do / change when you enable Advanced Security. Features like Secret Scanning with Push Protection are of course low hanging fruit, and can be shared internally without to much training: share a very short video of what will be turned on, why you’re doing that and the consequences of that (blocking secrets from entering the codebase). Then explain what you expect people to do with these alerts, to prevent them of just ignoring everything that comes in and marking it as “closed with will fix later” and then not doing anything about it.&lt;/p&gt;

&lt;h1 id=&quot;new-features-in-the-beta&quot;&gt;New features in the beta&lt;/h1&gt;
&lt;p&gt;The new features are available on the organization level (not on Enterprise yet) and are called “Code security configurations”. This allows you to set up a default configuration for some or all repositories in your organization. This is a great way to set up a default configuration for all repositories in your organization, and then tweak it for specific repositories if needed.&lt;/p&gt;

&lt;h3 id=&quot;new-policies&quot;&gt;New policies&lt;/h3&gt;
&lt;p&gt;It starts having ‘policies’ that you can deploy to repositories. That matches the administrative terminology that GitHub uses everywhere, so that makes sense.&lt;/p&gt;

&lt;p&gt;Since this is new, you only have the ‘GitHub recommended’ policies as well as a ‘legacy’ policy. The legacy policy just contains what settings you had set before to be the default for new repositories.&lt;/p&gt;

&lt;p&gt;The GitHub recommended policies will turn on the suggested settings for Dependabot, Secret scanning, and Code scanning. The UI does not really show you what that means at all, and I am very curious to learn what will happen if this recommendation changes in the future.&lt;/p&gt;

&lt;p&gt;During testing this turned out to be enabling the following settings on the repo level:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Dependency graph&lt;/li&gt;
  &lt;li&gt;Dependabot alerts&lt;/li&gt;
  &lt;li&gt;GitHub Advanced Security on the repo (will claim license seats)&lt;/li&gt;
  &lt;li&gt;CodeQL analysis with the default configuration, so running in the background (not visible in Actions) and non blocking in a pull request&lt;/li&gt;
  &lt;li&gt;Autofix for CodeQL is enabled (curious to see how that works with PR’s then! Probably follows the Check runs threshold) based on the alert that is generated, but I also would like to see if it annotates the PR at all.&lt;/li&gt;
  &lt;li&gt;Secret scanning and Push protection (Great! Low hanging fruit in my opinion)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/images/2024/20240427/20240427_01_Configurations.png&quot; alt=&quot;Screenshot of the UI showing &amp;quot;GitHub recommended&amp;quot; policies and your own policies&quot; /&gt;&lt;/p&gt;

&lt;p&gt;That least seems like a very sensible default to me, although I wish this would be more visible in the UI. I would like to see a list of what is enabled and what is not, so that you can gauge the consequences are of enabling these features.&lt;/p&gt;

&lt;h3 id=&quot;creating-a-policy&quot;&gt;Creating a policy&lt;/h3&gt;
&lt;p&gt;The screen to create a new policy is a dream. It is very clear and easy to use. The different functionalities (or GHAS ‘pillars’ as I like to call them) are grouped together for easy understanding, and then you can still enable all the features we had before. The “advanced security” label also nicely shows which of these features will require GHAS licenses for those repos.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2024/20240427/20240427_02_NewConfiguration.png&quot; alt=&quot;UI showing how to create a new configuration, with grouped blocks for each functionality, like &apos;Dependency settings&apos;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Also note at the bottom the flag ‘Use as default configuration for newly created repositories’. This is a great way to set up a default configuration for all repositories in your organization, and then tweak it for specific repositories if needed. The dropdown on the right hand side has the option to do this for only new public repos, or only for new private and internal repos. I assume the ‘private and internal’ option will also include ‘public’ repos, but I was not sure from the UI, so that could be improved. After testing it proved that this is indeed the case!&lt;/p&gt;

&lt;h3 id=&quot;applying-a-policy&quot;&gt;Applying a policy&lt;/h3&gt;
&lt;p&gt;You can apply policies by filtering the repo overview to the repositories you want to target with the new powerful filter bar that is available in certain places in the GitHub UI. Filter to the repos you want to apply the policy at and then click the ‘Apply configuration’ button. This will show you the policies that are available and you can select the one you want to apply.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2024/20240427/20240427_02_ApplyConfigurations.png&quot; alt=&quot;Screenshot of the UI showing how to apply a policy to a repository&quot; /&gt;&lt;/p&gt;

&lt;p&gt;After applying the policy to a repository, the repository admins will be able see that there has been a policy, although this is so subtle that I looked over it the first three times. I can also imagine an update later on in this same spot that lets repo admins choose from internal policies and adopt them easily during on-boarding.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2024/20240427/20240427_04_RepoLevel.png&quot; alt=&quot;Screenshot showing the subtle new panel that says &amp;quot;security configuration&amp;quot; with the mention of the policy name&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Repo level admins can still make changes to their repo settings and even lower the security settings if they want to. From an organization management standpoint I’m not sure if I like this or not, as this gives repo admins the option to lower our security standard. I would like to see a way to lock down the settings to the organization level, but I can also see that this means locking repo admins out of certain options. On the organization level you will see it when a repo with an applied policy has removed settings so that the repo does no longer match the policy. This information does not show up in the Audit log at the moment, so you will have to keep an eye on the settings yourself. I assume this is one of those things that will be added before this feature goes GA. With that I can at least monitor if the settings are still in place and act on it if needed.&lt;/p&gt;

&lt;p&gt;Do note that the setting “private vulnerability reporting” is not part of the policy settings. It is a feature outside of GHAS so I think that is the reason to not include it. As an admin I would like to see this in the policy settings as well, as it is a security feature that I would like to have enabled by default and promote everywhere. It depends of course on the maturity level of the organization, if they have Enterprise Cloud or Server, or maybe have a different internal process for reporting security vulnerabilities. So far I have seen a lot of companies not even having a policy, so having this on by default makes a lot of sense for them in my opinion.&lt;/p&gt;

&lt;h1 id=&quot;limitations&quot;&gt;Limitations&lt;/h1&gt;
&lt;p&gt;There are at the moment some limitations of using the new policies. This makes sense as it is only a beta, so some updates might come in the future. The limitations are:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Not available in “User space”, so not on your own repos. Which makes sense as this will probably (guessing here) be a paid feature for Teams and higher plans.&lt;/li&gt;
  &lt;li&gt;For free organizations it is only available for public repos, but not for private repos. It’s great this is free for public repos, it makes it easier to roll this out on all your repos and up your security game!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/images/2024/20240427/20240427_Limitiations.png&quot; alt=&quot;Screenshot showing that the policies only work on public repos on a free organization&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;summary&quot;&gt;Summary&lt;/h1&gt;
&lt;p&gt;Overall I really like the new way to make it faster to rollout defaults in your organization. I can image creating several policies in the form of level 1 to 3 at your company, and graduating teams from level to level:&lt;/p&gt;

&lt;p&gt;Level 1: Basic security settings, like secret scanning and push protection, together with the Dependency Graph and Alerts. It makes sense to enable Code Scanning in here to at least get the alerts, but having a communication plan with your engineers is key here to let them know that alerts will be found, and what we expect them to do with them (or not).&lt;br /&gt;
Level 2: Include Dependabot security updates and start blocking on PR’s with the &lt;a href=&quot;https://github.com/actions/dependency-review-action&quot;&gt;Dependency-Review action&lt;/a&gt; (not part of the policy options by the way).&lt;br /&gt;
Level 3: Also include version updates as well as the CodeQL analysis with blocking alerts in PR’s.&lt;/p&gt;

&lt;p&gt;This will really be helpful rolling out GHAS features across an organization, as we can roll this out really easily on a team-by-team basis in one go, instead of having to go repo-by-repo.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Running GHAzDo CodeQL on self-hosted runners</title>
			<link href="https://devopsjournal.io/blog/2024/01/22/Running-GHAzDO-CodeQL-on-selfhosted-runners"/>
			<updated>2024-01-22T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2024/01/22/Running-GHAzDO-CodeQL-on-selfhosted-runners</id>
			<content type="html">&lt;p&gt;When you start running CodeQL scans on your Azure DevOps environment on self-hosted runners, you’ll learn that you have to do one extra step and that is install (and keep up to date!) the CodeQL bundle on your self-hosted runners.&lt;/p&gt;

&lt;p&gt;If you don’t do this, you’ll get an error like this:&lt;br /&gt;
&lt;img src=&quot;/images/2024/20240122/20240122_01_ErrorMessage.png&quot; alt=&quot;Screenshot of a run on a self hosted runner&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Following the url in the error will bring you to the &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/devops/repos/security/configure-github-advanced-security-features?view=azure-devops&amp;amp;tabs=yaml#extra-prerequisites-for-self-hosted-agents&quot;&gt;docs&lt;/a&gt; where you might notice the following three bullets:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Pick the latest CodeQL release bundle from GitHub.&lt;/li&gt;
  &lt;li&gt;Download and unzip the bundle to the following directory inside the agent tool directory, typically located under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_work/_tool: ./CodeQL/0.0.0-[codeql-release-bundle-tag (i.e. codeql-bundle-v2.14.2)]/x64/&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Create an empty file at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./CodeQL/0.0.0-[codeql-release-bundle-tag (i.e. codeql-bundle-20221105)]/x64.complete&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Getting this configuration right took me longer then I like to admit, so here it is for future reference to get this correct next time:&lt;/p&gt;

&lt;h1 id=&quot;1-download-the-latest-version-of-the-bundle&quot;&gt;1. Download the latest version of the bundle&lt;/h1&gt;
&lt;p&gt;Get the bundle itself for the OS and bitness of the OS the runner is using. In my case I was executing the runner on my Windows 11 laptop, s I needed &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;codeql-bundle-win64.tar.gz&lt;/code&gt;:
&lt;img src=&quot;/images/2024/20240122/20240122_02_Versionnumber.png&quot; alt=&quot;Screenshot of the codeql-action that hosts the binaries&quot; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Note: If you need to automate this, then use the link &lt;a href=&quot;https://github.com/github/codeql-action/releases/latest&quot;&gt;https://github.com/github/codeql-action/releases/latest&lt;/a&gt; to quickly get to the latest version of the bundle.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1 id=&quot;2-place-the-contents-of-the-bundle-in-the-right-location-on-the-runner&quot;&gt;2. Place the contents of the bundle in the right location on the runner&lt;/h1&gt;
&lt;p&gt;Go to you runner and get the subfolder &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;codeql&lt;/code&gt; into the correct location. This took a couple of tries because the docs are confusing.&lt;/p&gt;

&lt;p&gt;The correct location looks like this:
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runner\_work\_tool\CodeQL\0.0.0-codeql-bundle-v2.15.5&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Where I have the following remarks:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runner&lt;/code&gt;: location where I have installed the runner service itself. This folder name is for you to choose.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0.0.0-codeql-bundle-v2.15.5&lt;/code&gt;: this is the version of the bundle you are using. Since I downloaded &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v2.15.5&lt;/code&gt; and this bundle is used for all previous versions, this is used in the folder name as well. During testing I found that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0.0.0-v2.15.5&lt;/code&gt; also works.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;3-create-the-complete-file-at-the-correct-folder&quot;&gt;3. Create the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.complete&lt;/code&gt; file at the correct folder&lt;/h1&gt;
&lt;p&gt;As the docs state, there needs to be a file with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bitness.complete&lt;/code&gt; name in the right location. I made the mistake of placing that into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x64&lt;/code&gt; folder, but it needs to be in the version folder. So in my case it needs to be in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0.0.0-codeql-bundle-v2.15.5&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With that, my folder structure looks like this:&lt;br /&gt;
&lt;img src=&quot;/images/2024/20240122/20240122_03_Folderstructure.png&quot; alt=&quot;Example of the folder structure, showing the x64.complete file in the version folder, and not in x64&quot; /&gt;&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Best viewed blogposts of 2023</title>
			<link href="https://devopsjournal.io/blog/2023/12/20/best-blogposts-2023"/>
			<updated>2023-12-20T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2023/12/20/best-blogposts-2023</id>
			<content type="html">&lt;p&gt;It’s that time of the year again! Time to look back at the most viewed blogposts of the year. I’m always amazed at the number of views some of these posts get. I’m also amazed at the number of people that find my blogposts useful. I’m glad I can help out!&lt;/p&gt;

&lt;p&gt;Here is the overview:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;#&lt;/th&gt;
      &lt;th&gt;Title&lt;/th&gt;
      &lt;th&gt;Published&lt;/th&gt;
      &lt;th&gt;Description&lt;/th&gt;
      &lt;th&gt;Views&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;1.&lt;/td&gt;
      &lt;td&gt;2022&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;/blog/2022/08/12/workflows-not-starting&quot;&gt;GitHub workflows not starting&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;🙀The most viewed for a reason! When your GitHub workflows are not starting up, there can be quite a few things causing this. Read this post to find out more!&lt;/td&gt;
      &lt;td&gt;17.2k&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;2.&lt;/td&gt;
      &lt;td&gt;2022&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;/blog/2022/01/03/GitHub-Tokens&quot;&gt;GitHub access tokens explained&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Personal Access Tokens can be a quick an easy way to test things out, but should not be used in production scenarios for a number of reasons 👺. In this post I explain why and what alternatives there are&lt;/td&gt;
      &lt;td&gt;6.7k&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;3.&lt;/td&gt;
      &lt;td&gt;2020&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;/blog/2020/04/05/netcore-nested-appsettings-json&quot;&gt;.NET core: nesting appSettings.json in the IDE&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;A three year old post that I jotted down for future reference. Seems like other people still have that same need! 🎉&lt;/td&gt;
      &lt;td&gt;4.5k&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;4.&lt;/td&gt;
      &lt;td&gt;2020&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;/blog/2020/11/24/github-actions-with-private-runner-iis&quot;&gt;GitHub Actions with private runner: deploy to IIS&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;🔍 Using a private runner for GitHub actions unlocks tons of scenario’s to deploy into your private production environment as well. Some folks thing it’s hard to do so on Windows and a classical setup with IIS. In this post I explain the moving parts in making this happen! IIRC I used the same setup to stop, install, and start Windows Services as well. 🦖🌴&lt;/td&gt;
      &lt;td&gt;4.2k&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;5.&lt;/td&gt;
      &lt;td&gt;2020&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;/blog/2020/11/08/Azure-DevOps-Git-Authenticate-With-PAT&quot;&gt;Authenticate to Azure DevOps with a Personal access token&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Authenticating to Azure DevOps using a personal access token takes a while to figure it out. For future reference I jotted it down. Still helping other folks after 3️⃣ years!&lt;/td&gt;
      &lt;td&gt;3.5k&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Looking at these things is always interesting for me. For the last three years I have been blogging mostly on GitHub, Advanced Security, and some Azure DevOps (mostly on Advanced Security on Azure DevOps). The top post is about a typical scenario that I see a lot of folks struggle with, so I am very happy this post gets found a lot to.&lt;/p&gt;

&lt;p&gt;Three of the top five posts are from 2020! That means a lot of folks still have the same need to know this as I did when I wrote the post. This is also the reason why I keep on blogging about these things. It means I can find these things myself, and apparently other folks find them useful as well 🤗.&lt;/p&gt;

&lt;p&gt;My top post from 2023 isn’t even in the top 10 of best viewed posts! It’s the post on &lt;a href=&quot;/blog/2023/05/23/GitHub-Advanced-Security-Azure-DevOps&quot;&gt;GitHub Advanced Security on Azure DevOps&lt;/a&gt;, where I explain the new features on Azure DevOps that where announced in the beginning of the year. These security features are available for anyone to use since November 2023 (if you need help with it, let me know!).&lt;/p&gt;

&lt;h1 id=&quot;blogposts-from-this-year-&quot;&gt;Blogposts from this year 📝&lt;/h1&gt;
&lt;p&gt;Looking back at blogging in 2023, I wrote 12 blogposts and shared my lessons learned or how I implemented things for customers. So even though I average a post a month or so, people still are able to find them and use them for their own benefit.&lt;/p&gt;

&lt;p&gt;For me the ones I looked at this year that stand out are:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2023/05/23/GitHub-Advanced-Security-Azure-DevOps&quot;&gt;GitHub Advanced Security on Azure DevOps&lt;/a&gt; - I’m a fan of Advanced Security, so this functionality becoming available for Azure DevOps is a big win for me.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2023/06/08/GITHUB_STEP_SUMMARY&quot;&gt;How to write to the GITHUB_STEP_SUMMARY&lt;/a&gt; - Very helpful to have a mental image of the moving parts. Instead of looking at the sparse GitHub docs for this, I refer to my own post almost quarterly at the moment.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2023/06/21/GitHub-Container-Action-Cleanup&quot;&gt;GitHub container based action cleanup&lt;/a&gt; - I’m a fan of container based actions, but it can be a bit tricky to clean up after yourself in a container based action.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2023/09/02/Get-alerts-from-GHAzDo&quot;&gt;Get alerts from GitHub Advanced Security for Azure DevOps&lt;/a&gt; - In the past months I created an extension for Azure DevOps to show &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=RobBos.GHAzDoWidget&quot;&gt;alert information&lt;/a&gt; in your dashboards, as I was missing that information in the default functionality. This post explains how I you can get those alerts from the REST API yourself (written when the docs were not avaiable yet).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;website-stats-&quot;&gt;Website stats 📈&lt;/h2&gt;
&lt;p&gt;For visitor numbers I use &lt;a href=&quot;https://plausible.io/&quot;&gt;Plausible.io&lt;/a&gt; and those numbers are publicly available &lt;a href=&quot;https://plausible.io/devopsjournal.io&quot;&gt;here&lt;/a&gt;.&lt;br /&gt;
A few of the numbers can be found below:&lt;/p&gt;

&lt;iframe plausible-embed=&quot;&quot; src=&quot;https://plausible.io/share/devopsjournal.io?auth=K5_P8Ud8iwNKboadStl1N&amp;amp;embed=true&amp;amp;theme=light&quot; scrolling=&quot;no&quot; frameborder=&quot;0&quot; loading=&quot;lazy&quot; style=&quot;width: 1px; min-width: 100%; height: 1600px;&quot;&gt;
&lt;/iframe&gt;
</content>
		</entry>
	
		<entry>
			<title>New LinkedIn Learning course! GitHub Advanced Security for Azure DevOps</title>
			<link href="https://devopsjournal.io/blog/2023/12/20/GHAzDo-LinkedInLearning"/>
			<updated>2023-12-20T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2023/12/20/GHAzDo-LinkedInLearning</id>
			<content type="html">&lt;h1 id=&quot;github-advanced-security-for-azure-devops&quot;&gt;GitHub Advanced Security for Azure DevOps&lt;/h1&gt;
&lt;p&gt;My newest LinkedInLearning Course is available now! This course is all about GitHub Advanced Security for Azure DevOps. It’s a great way to learn how to use the GitHub Advanced Security features in your Azure DevOps pipelines, with practical examples. There is even an example repository that you can use to follow along with the course. You can find the course on LinkedIn Learning: &lt;a href=&quot;https://www.linkedin.com/learning/learning-github-advanced-security-for-azure-devops/&quot;&gt;GitHub Advanced Security for Azure DevOps&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you want to read more about GitHub Advanced Security for Azure DevOps you can read my post &lt;a href=&quot;/blog/2023/05/23/GitHub-Advanced-Security-Azure-DevOps&quot;&gt;here&lt;/a&gt;. That’ll show you what this new functionality in Azure DevOps is all about.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.linkedin.com/learning/learning-github-advanced-security-for-azure-devops/&quot;&gt;&lt;img src=&quot;/images/2023/20231220/2023122-LinkedInLearning-GHAzDo.png&quot; alt=&quot;Photo of Rob &apos;gifting&apos; you the new course&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;course-overview&quot;&gt;Course overview&lt;/h1&gt;
&lt;p&gt;In the course I’ll take you through the three main pillars of Advanced Security for Azure DevOps, that can be used separately and help you shift your security practices left, as is a &lt;a href=&quot;https://www.youtube.com/watch?v=E4UD1dloNM8&amp;amp;ab_channel=RobBos&quot;&gt;recommend practice&lt;/a&gt; in DevSecOps.&lt;/p&gt;

&lt;p&gt;The three pillars of Advanced Security are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Dependency scanning: know what dependencies you are using, and if they have any known vulnerabilities in the versions you use.&lt;/li&gt;
  &lt;li&gt;Secret scanning: make sure you don’t accidentally commit secrets to your repository, and if you do, get notified so you can revoke the secret.&lt;/li&gt;
  &lt;li&gt;Code scanning: scan your code for known vulnerabilities, and get notified if you do.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These features can help you shift your security practices left, and thus help you find and fix security issues earlier in the development process. This can help you greatly reduce the cost of fixing security issues.&lt;/p&gt;

&lt;p&gt;Next to explaining how to use the features Advanced Security has, I also show examples of how to get an overview of your progress and alerts in either Defender for Cloud in Azure, or by using my &lt;a href=&quot;https://marketplace.visualstudio.com/items/RobBos.GHAzDoWidget&quot;&gt;GHAZDo Extension&lt;/a&gt; in Azure DevOps.&lt;/p&gt;

&lt;p&gt;Keep on learning!&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Dependabot alerts triaging in GitHub</title>
			<link href="https://devopsjournal.io/blog/2023/10/07/Dependabot-alert-triaging"/>
			<updated>2023-10-07T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2023/10/07/Dependabot-alert-triaging</id>
			<content type="html">&lt;p&gt;The GitHub UI displays a couple of helpful tips to use in triaging your Dependabot alerts which are super helpful. Unfortunately the User Interface does not show these filters in the filter bar yet, so I wanted to have a better overview of the filters I could use. I’ve listed them below:&lt;/p&gt;

&lt;h2 id=&quot;only-show-alerts-where-your-code-is-using-the-vulnerable-calls-of-the-dependency&quot;&gt;Only show alerts where your code is using the vulnerable calls of the dependency&lt;/h2&gt;
&lt;p&gt;This is very helpful in triaging the open alerts. Currently in limited preview for certain languages and certain package ecosystems. I hope they will expand this to more languages and package ecosystems.&lt;/p&gt;

&lt;p&gt;Instead of wading through all the open alerts where your code might (currently!) not call into the vulnerable part of the dependency code, you can filter down the list of alerts to the things you &lt;strong&gt;are&lt;/strong&gt; calling. Use this filter:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;is:open has:vulnerable-calls&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;filter-on-dependabot-auto-dismissal&quot;&gt;Filter on Dependabot auto dismissal&lt;/h2&gt;
&lt;p&gt;Dependabot now has new functionality to auto dismiss alerts that have low or medium severity. This is a great way to reduce the noise in your alerts list so you can filter on the important issues. You can filter on these alerts with this filter:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;is:closed resolution:auto-dismissed &lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;filter-on-only-runtime-dependencies&quot;&gt;Filter on only runtime dependencies&lt;/h2&gt;
&lt;p&gt;This is a great way to filter out the development dependencies from the runtime dependencies. This is the case for example when you use NPM as a package manager. You can use this filter to only show the runtime dependencies:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;is:open scope:runtime&lt;/code&gt;
Do make sure that you are not accidentally publishing to production with development dependencies. This is a common mistake that can lead to security issues, as the files are then still on disk, and could be used as an attack vector.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;filter-on-vulnerable-dependencies-where-a-patch-is-available&quot;&gt;Filter on vulnerable dependencies where a patch is available&lt;/h2&gt;
&lt;p&gt;This is helpful to focus on quick wins. Since there is a patch available that fixes the vulnerability, getting these dependencies upgraded can lead to fast results. You can use this filter to only show the alerts where a patch is available:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;is:open has:patch&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
		</entry>
	
		<entry>
			<title>Get alerts from GitHub Advanced Security for Azure DevOps</title>
			<link href="https://devopsjournal.io/blog/2023/09/02/Get-alerts-from-GHAzDo"/>
			<updated>2023-09-02T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2023/09/02/Get-alerts-from-GHAzDo</id>
			<content type="html">&lt;p&gt;GitHub Advanced Security for Azure DevOps (GHAzDo) builds on top of the functionality for GitHub Advanced Security and is giving you extra security tools to embed into your developer way of working. It’s a great way to get started with security in your Azure Pipelines and Azure repos and I’ve written about it before in &lt;a href=&quot;/blog/2023/05/25/GitHub-Advanced-Security-Azure-DevOps&quot;&gt;this blogpost&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id=&quot;loading-the-alerts-from-the-apis&quot;&gt;Loading the alerts from the API’s&lt;/h1&gt;

&lt;p&gt;Before starting with the Advanced Security API’s you’ll need to get the ID for the repository you are working with. You’ll need to have the project name and the repository name itself. With that you can make this API call:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;https://dev.azure.com/&amp;lt;PROJECT NAME&amp;gt;/_apis/git/repositories?api-version=7.1-preview.1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It will return you the list of repos the token you are using has access to. The repo object will look like this:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;5e5195e1-1b44-4d4b-9310-5d33ee2c4dc&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;eShopOnWeb&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://dev.azure.com/raj-bos/3651f6f0-74e5-48d7-8ff9-d62ae2464b1/_apis/git/repositories/5e5195e1-1b44-4d4b-9310-5d33ee2c4dc&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;project&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;3651f6f0-74e5-48d7-8ff9-d62ae2464b1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;GHAzDo trial&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://dev.azure.com/raj-bos/_apis/projects/3651f6f0-74e5-48d7-8ff9-d62ae2464b1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;state&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;wellFormed&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;revision&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;141&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;visibility&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;private&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;lastUpdateTime&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2023-07-26T18:51:26.227Z&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;defaultBranch&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;refs/heads/main&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;size&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;62610330&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;remoteUrl&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://raj-bos@dev.azure.com/raj-bos/GHAzDo%20trial/_git/eShopOnWeb&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;sshUrl&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;git@ssh.dev.azure.com:v3/raj-bos/GHAzDo%20trial/eShopOnWeb&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;webUrl&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://dev.azure.com/raj-bos/GHAzDo%20trial/_git/eShopOnWeb&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;isDisabled&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;isInMaintenance&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;From this you need the “id” field of the response. That can then be injected into the next API call:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;https://advsec.dev.azure.com/&amp;lt;PROJECT NAME&amp;gt;/&amp;lt;REPO NAME&amp;gt;/_apis/AdvancedSecurity/repositories/&amp;lt;REPO ID&amp;gt;/alerts?top=50&amp;amp;orderBy=severity&amp;amp;alertType=3&amp;amp;ref=refs/heads/main&amp;amp;states=1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The filtering options determine the response you will get back:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Param&lt;/th&gt;
      &lt;th&gt;Description&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;top&lt;/td&gt;
      &lt;td&gt;The number of alerts you want to get back.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;orderBy&lt;/td&gt;
      &lt;td&gt;The field you want to order the results by.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;criteria.alertType&lt;/td&gt;
      &lt;td&gt;The type of alert you want to get back. 1 = Dependency, 2 = Secrets, 3 = Code scanning&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;criteria.ref&lt;/td&gt;
      &lt;td&gt;The branch you want to get the alerts for, only needed when looking at code scanning alerts&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;criteria.states&lt;/td&gt;
      &lt;td&gt;The state of the alerts you want to get back. 1 = Open, 2 = Closed&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;You can also leave the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;alertType&lt;/code&gt; away from the url to get all alerts in one go. Do be aware that it will result in a different value for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;alertType&lt;/code&gt; in the response, instead of the numbers listed in the table above:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Value&lt;/th&gt;
      &lt;th&gt;Type&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;code&lt;/td&gt;
      &lt;td&gt;Code scanning alert&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;secret&lt;/td&gt;
      &lt;td&gt;Secret scanning alert&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;dependency&lt;/td&gt;
      &lt;td&gt;Dependency alert&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
</content>
		</entry>
	
		<entry>
			<title>Slides for Developer Week &apos;23</title>
			<link href="https://devopsjournal.io/blog/2023/06/29/Sessions-DWX23"/>
			<updated>2023-06-29T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2023/06/29/Sessions-DWX23</id>
			<content type="html">&lt;p&gt;I had the pleasure of speaking at Developer Week ‘23 in Nuremberg, Germany (&lt;a href=&quot;https://www.developer-week.de/speaker/#/speaker/19526-rob-bos&quot;&gt;link&lt;/a&gt;) this year. Below you can find the slides that go with my sessions that have all the links you’ve seen in the session.&lt;/p&gt;

&lt;h2 id=&quot;github-actions-beyond-cicd&quot;&gt;GitHub Actions: Beyond CI/CD&lt;/h2&gt;
&lt;p&gt;With GitHub Actions you can do so much more then just CI/CD! I’ve validated the links on my blogposts, automated my issue management and provided easy configuration of my trainings that sets up entire environments for the attendees!&lt;/p&gt;

&lt;p&gt;Join this session for more examples how you can use GitHub Actions to make your life easier!&lt;/p&gt;

&lt;p&gt;You will learn:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;GitHub Actions can do more then CI/CD&lt;/li&gt;
  &lt;li&gt;Real-life examples of automation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Download the slides from this &lt;a href=&quot;/slides/20230629%20DWX%20-%20GitHub%20Actions%20Beyond%20CI%20CD.pdf&quot;&gt;link&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;protect-yourself-against-supply-chain-attacks&quot;&gt;Protect yourself against Supply Chain Attacks&lt;/h2&gt;
&lt;p&gt;As an industry, we are using third party packages and building components for lots of things. In this supply chain, there are lots of places for vulnerabilities. They can then be used to attack your DevOps pipelines!&lt;/p&gt;

&lt;p&gt;In this session, I will go over some common attack examples and show you a way to prevent them from happening. There are frameworks available in the industry that guide you through the process of becoming more mature in protecting not only your source code and application but also the packages you use and the pipelines you build them with. I’ll demo some of GitHub’s features that help preventing these types of attacks.&lt;/p&gt;

&lt;p&gt;You will learn:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Why do we need to protect ourselves?&lt;/li&gt;
  &lt;li&gt;What things do we need to think about?&lt;/li&gt;
  &lt;li&gt;A framework to guide you through improving your security stance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Download the slides from this &lt;a href=&quot;/slides/20230629%20DWX%20-%20Protect%20yourself%20against%20supply%20chain%20attacks%20through%20your%20pipeline.pdf&quot;&gt;link&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;protect-your-code-with-github-security-features&quot;&gt;Protect your Code with GitHub Security Features&lt;/h2&gt;
&lt;p&gt;Creating modern software has a lot of moving parts. We all build on top of the shoulders of giants by leveraging closed/open source packages or containers that other people have shared. That makes securing our software a lot more complex as well!&lt;/p&gt;

&lt;p&gt;In this session, you’ll learn what possible attack vectors you need to look for, how to protect yourself against them and how to leverage GitHub’s features to make your life easier!&lt;/p&gt;

&lt;p&gt;You will learn:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Commit signing&lt;/li&gt;
  &lt;li&gt;Dependabot&lt;/li&gt;
  &lt;li&gt;Secret scanning&lt;/li&gt;
  &lt;li&gt;Code scanning using CodeQL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Download the slides from this &lt;a href=&quot;/slides/20230629%20DWX%20-%20Protect%20your%20code%20with%20GitHub%20security%20features.pdf&quot;&gt;link&lt;/a&gt;.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Cleaning up files changed by a GitHub Action that runs in a container</title>
			<link href="https://devopsjournal.io/blog/2023/06/21/GitHub-container-based-Action-cleanup"/>
			<updated>2023-06-21T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2023/06/21/GitHub-container-based-Action-cleanup</id>
			<content type="html">&lt;p&gt;A common issue we see with self-hosted runners is that they can leave behind files that were created or modified by the action. This is because the action runs in a container and the container is using a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;root&lt;/code&gt; user to do its work.&lt;/p&gt;

&lt;p&gt;The GitHub documentation says to run the the runner service as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;root&lt;/code&gt; as well, to have the most compatibility with most runners. This is not a good idea, as it can lead to security issues, so a lot of people run the runner service as a non-root user. This can lead to permission issues when the action touches files in the workspace directory that get’s mounted.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2023/20230621/aakanksha-panwar-SOOTeA8nL4o-unsplash.jpg&quot; alt=&quot;Photo of a ray of sun in between the clouds&quot; /&gt;&lt;/p&gt;
&lt;h5 id=&quot;photo-by-aakanksha-panwar-on-unsplash&quot;&gt;Photo by &lt;a href=&quot;https://unsplash.com/ko/@aakanksha_panwar?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Aakanksha Panwar&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/SOOTeA8nL4o?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/h5&gt;

&lt;h2 id=&quot;examples&quot;&gt;Examples&lt;/h2&gt;
&lt;p&gt;One common example is the &lt;a href=&quot;https://github.com/marketplace/actions/super-linter&quot;&gt;super-linter&lt;/a&gt; action. Depending on the checks that run, it can touch files on disc that then get owned by the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;root&lt;/code&gt; user.&lt;/p&gt;

&lt;p&gt;Another example is running the entire job inside of a container, with using the keyword &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;container&lt;/code&gt; on the job level:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;self-hosted&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu:22.04&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;echo &quot;Hello World&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The checkout action will create a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.git&lt;/code&gt; directory in the workspace directory with the repo contents, which will be owned by the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;root&lt;/code&gt; user as the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ubuntu-22.04&lt;/code&gt; container runs as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;root&lt;/code&gt;. The job itself will complete just fine, but the next time you run another job for this repository on the same runner, the checkout action will try to cleanup the $GITHUB_WORKSPACE directory to get a clean starting point, and will fail with a permission error since the job is not running as root, but as the non-root user the runner service is executing under.&lt;/p&gt;

&lt;p&gt;There are multiple ways to tackle this issue:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Prevent it from happening: Run the runner service as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;root&lt;/code&gt; and run the job as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;root&lt;/code&gt; as well. This is recommended by GitHub to prevent this from happening, that is how the service has been designed. Most people I talk with do not agree since the user has to much permissions and it can lead to security issues.&lt;/li&gt;
  &lt;li&gt;Change the action to not run as root, which is not realistic when using actions from the public marketplace.&lt;/li&gt;
  &lt;li&gt;Get the user to cleanup inside of the container, or add a cleanup action in their job.&lt;/li&gt;
  &lt;li&gt;Add cleanup configuration on the container level configuration in the runner&lt;/li&gt;
  &lt;li&gt;Add cleanup configuration on the runner configuration itself&lt;/li&gt;
  &lt;li&gt;Do not run the container with any persistence: run as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ephemeral&lt;/code&gt;, where the runner is alive for a single job execution, and then gets completely deleted and cleaned up so there is no reuse.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’ll go over the last 4 options in this post.&lt;/p&gt;

&lt;h2 id=&quot;get-the-user-to-cleanup-inside-of-the-container-or-add-a-cleanup-action-in-their-job&quot;&gt;Get the user to cleanup inside of the container, or add a cleanup action in their job.&lt;/h2&gt;
&lt;p&gt;You could hunt for the jobs that cause this issue, and ‘ask’ the user to add a cleanup action in the job itself. There is for example a &lt;a href=&quot;https://github.com/asilbek99/action-cleanup&quot;&gt;cleanup action&lt;/a&gt; available in the marketplace that runs in a container that uses root, and that cleans up the workspace directory. This is not a good solution, as it requires a lot of manual work, and you will have to keep track of the jobs that cause this issue. The following options are probably manageable in a better way.&lt;/p&gt;

&lt;h2 id=&quot;add-cleanup-configuration-on-the-container-level-configuration-in-the-runner&quot;&gt;Add cleanup configuration on the container level configuration in the runner&lt;/h2&gt;
&lt;p&gt;You can configure the runner with specific customization commands that get executed before and after the job, as well as other options:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;prepare_job: Called when a job is started.&lt;/li&gt;
  &lt;li&gt;cleanup_job: Called at the end of a job.&lt;/li&gt;
  &lt;li&gt;run_container_step: Called once for each container action in the job.&lt;/li&gt;
  &lt;li&gt;run_script_step: Runs any step that is not a container action. 
See the entire configuration and examples &lt;a href=&quot;https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/customizing-the-containers-used-by-jobs&quot;&gt;here&lt;/a&gt;. This is rather complex and more suited for configuring sidecars or other complex scenarios. I prefer to use the next option.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;add-cleanup-configuration-on-the-runner-configuration-itself&quot;&gt;Add cleanup configuration on the runner configuration itself&lt;/h2&gt;
&lt;p&gt;By setting 2 environment variables before the job starts, you can run specific scripts before and after the job starts. The completed job can then be a docker run command that mounts the $GITHUB_WORKSPACE and cleans everything up while running as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;root&lt;/code&gt;. This is the easiest option in my opinion:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;ACTIONS_RUNNER_HOOK_JOB_STARTED: The script defined in this environment variable is triggered when a job has been assigned to a runner, but before the job starts running.&lt;/li&gt;
  &lt;li&gt;ACTIONS_RUNNER_HOOK_JOB_COMPLETED: The script defined in this environment variable is triggered after the job has finished processing.
Find more information in the documentation &lt;a href=&quot;https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/running-scripts-before-or-after-a-job&quot;&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;do-not-run-the-container-with-any-persistence&quot;&gt;Do not run the container with any persistence&lt;/h2&gt;
&lt;p&gt;The best advice is to always start runners as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ephemeral&lt;/code&gt;, where the runner is alive for a single job execution, and then gets completely deleted and cleaned up so there is no reuse. You will need to provide a mechanism to cleanup the Virtual Machine or Container setup where you execute the runner on. The best method I’ve found is to use &lt;a href=&quot;https://github.com/actions/actions-runner-controller&quot;&gt;Actions Runner Controller&lt;/a&gt; where the runner is executing inside of a container that gets deleted after the job is done. This will prevent any data to linger around on the runner as well, and gets you a clean execution environment every time.&lt;/p&gt;

&lt;p&gt;There might be some valid reasons to still have a persistent runner, but I would recommend to use ephemeral runners as much as possible.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Writing to the $GITHUB_STEP_SUMMARY with the core npm package</title>
			<link href="https://devopsjournal.io/blog/2023/06/08/GITHUB_STEP_SUMMARY"/>
			<updated>2023-06-08T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2023/06/08/GITHUB_STEP_SUMMARY</id>
			<content type="html">&lt;p&gt;Every time I need to write to the GITHUB_STEP_SUMMARY in GitHub Actions from the &lt;a href=&quot;https://github.com/actions/github-script&quot;&gt;actions/github-script&lt;/a&gt; action (or from Typescript), I need to search for the &lt;a href=&quot;https://github.blog/2022-05-09-supercharging-github-actions-with-job-summaries/&quot;&gt;blogpost that announced&lt;/a&gt; it’s existence. So I’m writing this blogpost to make it easier for myself to find it a lot easier, including some working examples.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2023/20230608/markus-winkler-aYPtEknQmXE-unsplash.jpg&quot; alt=&quot;Photo of around 20 white puzzle pieces against a white background&quot; /&gt;&lt;/p&gt;
&lt;h5 id=&quot;photo-by-markus-winkler-on-unsplash&quot;&gt;Photo by &lt;a href=&quot;https://unsplash.com/@markuswinkler?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Markus Winkler&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/aYPtEknQmXE?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/h5&gt;

&lt;p&gt;The code for the summaries lives in the &lt;a href=&quot;https://github.com/actions/toolkit/blob/main/packages/core/src/summary.ts&quot;&gt;actions/core&lt;/a&gt; package on npm, but figuring out how to use it can be a bit hard. The only example I’ve seen is in the blogpost I mentioned.&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;core&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;@actions/core&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; 
  
  &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;core&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;summary&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addHeading&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Test Results&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addCodeBlock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;generateTestResults&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addTable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}],&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;foo.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Pass ✅&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;bar.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Fail ❌&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;test.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Pass ✅&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addLink&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;View staging deployment!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;https://github.com&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This does a lot of things at the same time, but we get the general idea that you can:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;add headings&lt;/li&gt;
  &lt;li&gt;add code blocks&lt;/li&gt;
  &lt;li&gt;add tables&lt;/li&gt;
  &lt;li&gt;add links
And at the end you need to write the summary itself, which will be added to the file in the GITHUB_STEP_SUMMARY environment variable.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;working-with-the-table-output&quot;&gt;Working with the table output&lt;/h2&gt;
&lt;p&gt;There are no methods to break the table into chunks, like:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Add a header&lt;/li&gt;
  &lt;li&gt;Add a row&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The only method there is, is adding the table in one go, with each row as an array of objects, and some configuration in the first row as that will define if the cell is a header or not. So assuming you have an array of results that you want to show, you can convert that array with properties into an array of rows, with each property value being an item in the row array.&lt;/p&gt;

&lt;p&gt;The interesting thing I ran into, is that the row cells &lt;strong&gt;must be a string&lt;/strong&gt;. Sending in integers for example does not work.
Take the following example:&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;core&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;summary&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addHeading&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Example&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addTable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;
                        &lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Topic&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Count&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Public&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}],&lt;/span&gt;
                        &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;foo.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
                        &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;bar.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
                        &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;test.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
                      &lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;In this example, all rows will be added to the summary, and as long as the content is a valid string, it will be shown in the table as well. In this example, the values in the last row are integers, and they will be not visible in the table.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2023/20230608/20230608_ExampleOutput.png&quot; alt=&quot;Screenshot of the table output, with the integer values missing in the last row&quot; /&gt;&lt;/p&gt;

&lt;p&gt;A full example of creating the header array with hardcoded cells, and then adding the rows from an array of objects can be seen below. Here I have an array stored as output in a previous step, so I read that file and map it (as string values!) to an array containing the rows.
The next step is to join the two arrays (header + summary) and pass that to the addTable method.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Show information in the GITHUB_STEP_SUMMARY&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/github-script@v6&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;summaryFile&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;$&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; 
    &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;see below in other markup for better readability&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;summary&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;readFileSync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;summaryFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;utf8&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// make the heading array for the core.summary method&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;headingArray&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Topic&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Count&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Public&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Internal&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Private&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}]&lt;/span&gt;
    
        &lt;span class=&quot;c1&quot;&gt;// convert the summary array into an array that can be passed into the core.summary method&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;summaryArray&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;summary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;internal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;private&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()])&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// join the two arrays&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;tableArray&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;headingArray&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;summaryArray&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;core&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;summary&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addHeading&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`Topics used on repos in the [&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;] organization`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addTable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;tableArray&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;writing-raw-text-to-the-summary&quot;&gt;Writing raw text to the summary&lt;/h1&gt;
&lt;p&gt;If you want to add some lines of text to the summary with this, then let me save you some time on figuring this out (writing this for a friend 🙈):&lt;br /&gt;
You are writing the raw text as &lt;strong&gt;Markdown&lt;/strong&gt;, which I often forget. That means that everything has a meaning, especially after a header!&lt;/p&gt;

&lt;p&gt;Here is an example of some of my logging:. The end of lines are also needed!&lt;/p&gt;
&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;core&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;summary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addHeading&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Repo info&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                      &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addRaw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;``&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addEOL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
                      &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addRaw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`Total repos: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;repos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;  `&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addEOL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
                      &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addRaw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`Large repos: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;largerRepoCount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;  `&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addEOL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
                      &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addRaw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`Gitattributes: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;largerRepoHasGitAttributes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;  `&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addEOL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
                      &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;need-to-add-a-mermaid-diagram&quot;&gt;Need to add a mermaid diagram?&lt;/h2&gt;
&lt;p&gt;Notice the quotes in the exampe below, that’s where I went wrong the first few times:&lt;/p&gt;
&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;core&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;summary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addHeading&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Repo info&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addRaw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;```mermaid&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addEOL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
                  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addRaw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`pie title Repo size overview`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addEOL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
                  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addRaw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&quot;Large (&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;largerRepoCount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;)&quot;: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;largerRepoCount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addEOL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
                  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addRaw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&quot;Small (&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;normalRepoCount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;: ${normalRepoCount}`).addEOL()                          
                  .addRaw(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;```&quot;).addEOL()
                  .write()
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;Note:
Keep in mind that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;core.summary.write()&lt;/code&gt; method writes the entire buffer to the summary file, &lt;strong&gt;but does not clean that buffer&lt;/strong&gt;. That means that if you use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;write()&lt;/code&gt;, then add more info and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;write()&lt;/code&gt; again, you will get the first buffer &lt;strong&gt;as well as the second buffer&lt;/strong&gt;! I have not found a way to clean the buffer, so make sure you only call the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;write()&lt;/code&gt; only once!&lt;/p&gt;
&lt;/blockquote&gt;
</content>
		</entry>
	
		<entry>
			<title>Speaking at GOTO; Aarhus 2023</title>
			<link href="https://devopsjournal.io/blog/2023/05/24/goto-aarhus"/>
			<updated>2023-05-24T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2023/05/24/goto-aarhus</id>
			<content type="html">&lt;p&gt;I will be speaking at GOTO; Aarhus &lt;a href=&quot;https://gotoaarhus.com/2023&quot;&gt;link&lt;/a&gt; this year. 
Below you can find the slides that go with my sessions that have all the links you’ve seen in the session.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2023/20230524/20230523_OpeningSlide.png&quot; alt=&quot;Opening slide of the presentation&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;protect-your-code-with-github-security-features&quot;&gt;Protect your Code with GitHub Security Features&lt;/h2&gt;
&lt;p&gt;Creating modern software has a lot of moving parts. We all build on top of the shoulders of giants by leveraging closed/open source packages or containers that other people have shared. That makes securing our software a lot more complex as well!&lt;/p&gt;

&lt;p&gt;In this session, you’ll learn what possible attack vectors you need to look for, how to protect yourself against them and how to leverage GitHub’s features to make your life easier!&lt;/p&gt;

&lt;p&gt;You will learn:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Commit signing&lt;/li&gt;
  &lt;li&gt;Dependabot&lt;/li&gt;
  &lt;li&gt;Secret scanning&lt;/li&gt;
  &lt;li&gt;Code scanning using CodeQL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Download the slides from &lt;a href=&quot;/slides/20230524_GOTO_Aarhus_Protect_your_code_with_GitHub_security_features.pdf&quot;&gt;this link here&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id=&quot;pictures&quot;&gt;Pictures&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;/images/2023/20230524/Rob_OnStage2.png&quot; alt=&quot;Rob on stage&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2023/20230524/Rob_OnStage.jpg&quot; alt=&quot;Rob on stage in Aarhus&quot; /&gt;&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>GitHub Advanced Security for Azure DevOps</title>
			<link href="https://devopsjournal.io/blog/2023/05/23/GitHub-Advanced-Security-Azure-DevOps"/>
			<updated>2023-05-23T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2023/05/23/GitHub-Advanced-Security-Azure-DevOps</id>
			<content type="html">&lt;p&gt;Microsoft is bringing some of the GitHub Advanced Security tools to Azure DevOps. I have been playing with it for a while and they have presented the latest state at Microsoft Build 2023, which includes a  &lt;a href=&quot;https://analyticsindiamag.com/microsoft-makes-github-advanced-security-for-azure-devops-available-in-public-preview/&quot;&gt;Public Preview!&lt;/a&gt;. That means you can try it out yourself, and I can finally share my experiences with you! Since I teach a lot people on how to use this on GitHub, you can find some of the differences between the two implementations in this post as well 😉.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2023/20230523/adi-goldstein-EUsVwEOsblE-unsplash.jpg&quot; alt=&quot;High tech security image&quot; /&gt;&lt;/p&gt;
&lt;h5 id=&quot;photo-by-adi-goldstein-on-unsplash&quot;&gt;Photo by &lt;a href=&quot;https://unsplash.com/@adigold1?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Adi Goldstein&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/EUsVwEOsblE?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/h5&gt;

&lt;h2 id=&quot;what-is-github-advanced-security&quot;&gt;What is GitHub Advanced Security?&lt;/h2&gt;
&lt;p&gt;GitHub Advanced Security (GHAS) is a set of tools that help you secure your code and your pipelines. It includes the following tools:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Code scanning&lt;/li&gt;
  &lt;li&gt;Dependabot / Dependency scanning&lt;/li&gt;
  &lt;li&gt;Secret scanning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to dive deeper into that functionality, check out my LinkedIn Learning Course &lt;a href=&quot;https://devopsjournal.io/blog/2022/10/19/LinkedIn-Learning-GHAS&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;dependabot--dependency-scanning&quot;&gt;Dependabot / Dependency scanning&lt;/h3&gt;
&lt;p&gt;Scans you repository for known manifest files, like package.json, and checks if there are any vulnerabilities in the packages you are using. It will then create a pull request to update the package to a newer version that does not have the vulnerability. It can also automate creating pull requests for version updates as well.&lt;/p&gt;

&lt;h3 id=&quot;secret-scanning&quot;&gt;Secret scanning&lt;/h3&gt;
&lt;p&gt;Scans your repository for known secrets, like passwords, and notifies you if it finds any. It will also notify you if you push a commit that contains a secret. It will also scan your pull requests for secrets and notify you if it finds any.&lt;/p&gt;

&lt;h3 id=&quot;code-scanning&quot;&gt;Code scanning&lt;/h3&gt;
&lt;p&gt;Scans your code for known vulnerabilities using CodeQL (or another Static Application Security Tool, aka SAST). It can also scan your pull requests for vulnerabilities and notify you if it finds any.&lt;/p&gt;

&lt;h1 id=&quot;github-advanced-security-for-azure-devops&quot;&gt;GitHub Advanced Security for Azure DevOps&lt;/h1&gt;
&lt;p&gt;Microsoft is bringing the features from GHAS into Azure DevOps as customers have been asking for this for a while. It’s an example of the way they operate both products now, since the new features will get build for GitHub first, and if it makes sense, they might build some of these features into Azure DevOps as well.&lt;/p&gt;

&lt;p&gt;You can see the synergy here as well as of course: any known secret scanning patterns from GHAS can also be used in Azure DevOps, since they can share that between the companies.&lt;/p&gt;

&lt;h2 id=&quot;how-to-get-started-with-github-advanced-security-for-azure-devops&quot;&gt;How to get started with GitHub Advanced Security for Azure DevOps&lt;/h2&gt;

&lt;p&gt;First you will need to enable Advanced Security on the repository level, for which you will need Project Admin level access of course:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2023/20230523/20230518_EnablingGHAS.png&quot; alt=&quot;Screenshot of getting to the repository settings, where you can find a new setting &apos;Advanced Security&apos; that can be toggled on/off&quot; /&gt;&lt;/p&gt;

&lt;p&gt;After enabling the feature (on a repo by repo basis, since it will cost you license seats in the future), you will get a new ‘Advanced Security’ tab in your repository menu. This will show you the results of the scans that have been done on your repository.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2023/20230523/20230518%20GHAZDo%20overview.png&quot; alt=&quot;Overview of the Advanced Security alerts for a repo in Azure DevOps&quot; /&gt;&lt;/p&gt;

&lt;p&gt;After enabling the feature you will get some extra menu options left and right, as well as extra tasks for your Azure Pipelines.&lt;/p&gt;

&lt;h1 id=&quot;turning-on-secret-scanning&quot;&gt;Turning on Secret scanning&lt;/h1&gt;
&lt;p&gt;Secret scanning is turned on automatically when you turn on Advanced Security. It will start a background job that will scan your repository for secrets. It will also scan you repos entire history: all commits and branches will be checked, for known secret patterns. One additional feature is ‘push protection’. This will prevent you from pushing a commit that contains a secret. This is a great feature to prevent secrets from being pushed to your repository and ‘stop the leak’ of new alerts flowing in.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2023/20230523/20230518%20Secret%20scanning%20push%20protection.png&quot; alt=&quot;UI to enable push protection&quot; /&gt;&lt;/p&gt;

&lt;p&gt;#&lt;/p&gt;
&lt;h1 id=&quot;using-dependency-scanning&quot;&gt;Using dependency scanning&lt;/h1&gt;
&lt;p&gt;To get started with Dependency scanning you need to inject it into a pipeline. This can be done by adding the following task to your pipeline:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;AdvancedSecurity-Dependency-Scanning@1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The next time this pipeline will run, it will scan you repository for known manifest files, like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.csproj&lt;/code&gt; for C# projects, or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;package.json&lt;/code&gt; for NodeJS projects. From those manifest files it gathers the information about the packages you are pulling in, and can check the underlying ecosystem for the dependencies of those packages as well (libraries build on other libraries as well). It will then check if there are any vulnerabilities in the packages you are using. You can check each package you use, and all of their dependencies as well, with their version numbers against the National Vulnerability Database (&lt;a href=&quot;https://nvd.nist.gov/&quot;&gt;NVD&lt;/a&gt; from the NIST institute). Also registered in the NVD is which version is no longer vulnerable (if applicable).&lt;/p&gt;

&lt;p&gt;On GitHub that information is used to create a pull request to update the package to a newer version that does not have the vulnerability. It can also automate creating pull requests for version updates as well. This is not available for GitHub Advanced Security for Azure DevOps. I do expect this will be added later, but for now you will have to create the pull requests yourself.&lt;/p&gt;

&lt;p&gt;In the alert you will get information on the package version that you use, the first non-vulnerable version, as well as the type of vulnerability, so that you can learn on the type of attack vector for this alert.&lt;br /&gt;
&lt;img src=&quot;/images/2023/20230523/20230518%20Dependency%20Alert.png&quot; alt=&quot;Screenshot of a Dependency alert&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;using-code-scanning&quot;&gt;Using Code Scanning&lt;/h1&gt;
&lt;p&gt;For enabling Code Scanning you need to inject CodeQL into an Azure Pipeline as well (other SAST tools will work as well, as long as they upload a &lt;a href=&quot;https://sarifweb.azurewebsites.net/&quot;&gt;SARIF&lt;/a&gt; file):&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;AdvancedSecurity-Codeql-Init@1&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;inputs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;languages&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;csharp&apos;&lt;/span&gt;

    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;AdvancedSecurity-Codeql-Autobuild@1&lt;/span&gt;

    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;AdvancedSecurity-Codeql-Analyze@1&lt;/span&gt;

    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;AdvancedSecurity-Publish@1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There are a couple of steps to note here:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Init will create a local, empty database that will be filled with all the code paths your code is taking (for example: Method A is calling Method B). You also feed it one of the supported languages (C#, Java, C/C++, Python, JavaScript/TypeScript, Go, or Ruby).&lt;/li&gt;
  &lt;li&gt;The auto-build step will try to build you application, based on known patterns for the language you have selected. Sometimes the auto build fails for certain projects, you can then use your own build steps in its place.&lt;/li&gt;
  &lt;li&gt;With Analyze you run all the CodeQL queries against the database that was created in step 1. This will also create a SARIF file that contains all the results of the queries (if a query has a result, it will become an alert).&lt;/li&gt;
  &lt;li&gt;The Publish step will actually upload the SARIF file to the Advanced Security service, which will then show you the results in the UI.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The queries that will be run can be found in the CodeQL repository on &lt;a href=&quot;https://github.com/github/codeql&quot;&gt;github.com&lt;/a&gt;. There is a default set with low noise ratios, but you can also use &lt;a href=&quot;https://docs.github.com/en/enterprise-cloud@latest/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/customizing-code-scanning#using-queries-in-ql-packs&quot;&gt;extended queries&lt;/a&gt; by configuring them this way:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;AdvancedSecurity-Codeql-Analyze@1&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;inputs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;querysuite&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;security-and-quality&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h5 id=&quot;note-that-in-azure-devops-you-configure-this-on-the-analyze-task-where-as-you-do-that-in-github-in-the-init-step&quot;&gt;Note that in Azure DevOps you configure this on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Analyze&lt;/code&gt; task where as you do that in GitHub in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Init&lt;/code&gt; step.&lt;/h5&gt;

&lt;p&gt;Do be aware that adding more extensive queries will increase the duration of the pipeline, as well as the amount of alerts you will get: the extended queries have a bit more noise and potentially also more false positives. For example from my demo application I went from 4 normal alerts to 7 extra alerts when I added the extended queries from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;security-and-quality&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The advise is to run these queries at &lt;em&gt;least&lt;/em&gt; on a pull request, as well as on a schedule: the community who builds these queries is super active (150~200 pull requests a month in that repository), so you will get new queries that might find vulnerabilities in your code.&lt;/p&gt;

&lt;p&gt;When a result of the queries is present, you will see those come by in the logs as well:&lt;br /&gt;
&lt;img src=&quot;/images/2023/20230523/20230518%20Codescanning%20alerts%20in%20logs.png&quot; alt=&quot;Screenshot of the log indicating it found two issues&quot; /&gt;&lt;/p&gt;

&lt;p&gt;From the upload of the SARIF file you will then get the alerts generated in the Advanced Security overview:
&lt;img src=&quot;/images/2023/20230523/20230518%20CodeScanning%20Alerts.png&quot; alt=&quot;Screenshot of the code scanning alerts&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When you open up an alert you get more information on the detail page:
&lt;img src=&quot;/images/2023/20230523/20230518%20CodeScanning%20Alert%20detail.png&quot; alt=&quot;Screenshot of the alert detail page&quot; /&gt;&lt;/p&gt;

&lt;p&gt;From here you can again learn more about the vulnerability found, with links back to the Common Weakness Enumeration number (CWE) on the &lt;a href=&quot;https://cwe.mitre.org&quot;&gt;Mitre site&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id=&quot;addressing-the-alerts&quot;&gt;Addressing the alerts&lt;/h1&gt;
&lt;p&gt;To fix the alerts you will need to actually fix the finding it self. There is not yet a way to dismiss the alerts in Azure DevOps (something we do have on the GitHub side).&lt;/p&gt;

&lt;h2 id=&quot;fixing-secret-scanning-alerts&quot;&gt;Fixing secret scanning alerts&lt;/h2&gt;
&lt;p&gt;For secret scanning the UI currently shows the warning that you need to treat this secret as a leaked value, and fixing can only be done by revoking the secret.&lt;br /&gt;
&lt;img src=&quot;/images/2023/20230523/20230518%20Secret%20scanning%20alert.png&quot; alt=&quot;Screenshot of a secret scanning alert showing you need to revoke the secret&quot; /&gt; &lt;br /&gt;
Since there is no way to dismiss this alert through the UI yet, the only way to get rid of this alert is to rewrite the history of the repository so that the commit is no longer in the history. I expect that dismissing alerts will be added in the future, but for now this is the only way to get rid of the alert.&lt;/p&gt;

&lt;h2 id=&quot;fixing-dependency-scanning-alerts&quot;&gt;Fixing dependency scanning alerts&lt;/h2&gt;
&lt;p&gt;To get rid of a dependency scanning alert you will need to create a pull request to upgrade the dependency to a version that is no longer vulnerable. When the detection scan runs again, the alert will get closed. You have a link back to the pipeline that detected that the vulnerable dependency is no longer present: 
&lt;img src=&quot;/images/2023/20230523/20230518_DependencyAlertClosed.png&quot; alt=&quot;Screenshot of the closed alert&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;addressing-code-scanning-alerts&quot;&gt;Addressing code scanning alerts&lt;/h2&gt;
&lt;p&gt;Fixing Code Scanning alerts is a bit more involved, as you will need to actually fix the code that is causing the alert. After fixing the issue, the next scan will close the alert and will link back to the scan that closed the alert. From it, you can find the commit that fixed the issue as well, so you have that end-to-end traceability.&lt;/p&gt;

&lt;p&gt;Note that currently you cannot exclude code from the CodeQL scanning (on GitHub you can). That also means that your unit test suite for example, will get scanned as well, with a good change it finds some issues there as well (I had the same issue with my test project).&lt;/p&gt;

&lt;h2 id=&quot;dashboarding&quot;&gt;Dashboarding&lt;/h2&gt;
&lt;p&gt;For an overall overview of how you are doing across the entire project or organization, you’ll have to look at &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/defender-for-cloud/defender-for-devops-introduction&quot;&gt;Microsoft Defender for DevOps&lt;/a&gt;, which is a tool in Microsoft Azure. Since I don’t have Defender integrated yet (I think you need a feature flag to enable that), I don’t have any screenshots yet. I will add them when I have the access!&lt;/p&gt;

&lt;p&gt;The integration does make sense from the Azure perspective and integrate it in the tools folks on Azure Cloud use. But if you only use Azure DevOps, then I would expect an overview to find repeat patterns or dependencies in you Azure DevOps Organization as well. I would not be surprised if they add that overview somewhere in the future.&lt;/p&gt;

&lt;h2 id=&quot;summarizing&quot;&gt;Summarizing&lt;/h2&gt;
&lt;p&gt;Overall, most of the initial features are already in, with definitely some different choices made comparing it with GitHub’s implementation (for example dashboarding). You can see it is early days, but I expect the rest of the features / finetuning will happen fast, now that the initial part has been integrated (which is often the most work).&lt;/p&gt;

&lt;p&gt;We now have good security features integrated into Azure DevOps, right into the developer experience, which is where this needs to land in my opinion: with the folks who can act on the findings and fix them early on in their process. I hope they integrate the &lt;a href=&quot;https://github.com/actions/dependency-review-action&quot;&gt;dependency-review-action&lt;/a&gt; equivalent for Azure DevOps next, so developers will also be enabled to stop the leak (of incoming vulnerable dependencies).&lt;/p&gt;

&lt;p&gt;If you want to know more or have questions, use the comments below!&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Speaking at VSLive! Nashville 2023</title>
			<link href="https://devopsjournal.io/blog/2023/05/17/VSLive-Nashville"/>
			<updated>2023-05-17T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2023/05/17/VSLive-Nashville</id>
			<content type="html">&lt;p&gt;I had the pleasure of speaking at VS LIVE! in Nashville &lt;a href=&quot;https://vslive.com/events/nashville-2023/home.aspx&quot;&gt;link&lt;/a&gt; this year. Below you can find the slides that go with my sessions that have all the links you’ve seen in the session.&lt;/p&gt;

&lt;h2 id=&quot;w19-github-actions-beyond-cicd&quot;&gt;W19 GitHub Actions: Beyond CI/CD&lt;/h2&gt;
&lt;p&gt;With GitHub Actions you can do so much more then just CI/CD! I’ve validated the links on my blogposts, automated my issue management and provided easy configuration of my trainings that sets up entire environments for the attendees!&lt;/p&gt;

&lt;p&gt;Join this session for more examples how you can use GitHub Actions to make your life easier!&lt;/p&gt;

&lt;p&gt;You will learn:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;GitHub Actions can do more then CI/CD&lt;/li&gt;
  &lt;li&gt;Real-life examples of automation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Download the slides from this &lt;a href=&quot;/slides/20230517%20VSLIVE%20-%20GitHub%20Actions%20Beyond%20CI%20CD.pdf&quot;&gt;link&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;w23-protect-yourself-against-supply-chain-attacks&quot;&gt;W23 Protect yourself against Supply Chain Attacks&lt;/h2&gt;
&lt;p&gt;As an industry, we are using third party packages and building components for lots of things. In this supply chain, there are lots of places for vulnerabilities. They can then be used to attack your DevOps pipelines!&lt;/p&gt;

&lt;p&gt;In this session, I will go over some common attack examples and show you a way to prevent them from happening. There are frameworks available in the industry that guide you through the process of becoming more mature in protecting not only your source code and application but also the packages you use and the pipelines you build them with. I’ll demo some of GitHub’s features that help preventing these types of attacks.&lt;/p&gt;

&lt;p&gt;You will learn:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Why do we need to protect ourselves?&lt;/li&gt;
  &lt;li&gt;What things do we need to think about?&lt;/li&gt;
  &lt;li&gt;A framework to guide you through improving your security stance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Download the slides from this &lt;a href=&quot;/slides/20230517%20VSLIVE%20-%20Protect%20yourself%20against%20supply%20chain%20attacks%20through%20your%20pipeline.pdf&quot;&gt;link&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;th05-protect-your-code-with-github-security-features&quot;&gt;TH05 Protect your Code with GitHub Security Features&lt;/h2&gt;
&lt;p&gt;Creating modern software has a lot of moving parts. We all build on top of the shoulders of giants by leveraging closed/open source packages or containers that other people have shared. That makes securing our software a lot more complex as well!&lt;/p&gt;

&lt;p&gt;In this session, you’ll learn what possible attack vectors you need to look for, how to protect yourself against them and how to leverage GitHub’s features to make your life easier!&lt;/p&gt;

&lt;p&gt;You will learn:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Commit signing&lt;/li&gt;
  &lt;li&gt;Dependabot&lt;/li&gt;
  &lt;li&gt;Secret scanning&lt;/li&gt;
  &lt;li&gt;Code scanning using CodeQL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Download the slides from this &lt;a href=&quot;/slides/20230518%20VSLIVE%20-%20Protect%20your%20code%20with%20GitHub%20security%20features.pdf&quot;&gt;link&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;photos&quot;&gt;Photos&lt;/h2&gt;
&lt;p&gt;Met some nice folks and had a great time! Thanks for having me!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2023/20230517/IMG_0315.JPG&quot; alt=&quot;Session from Leoni Lobel&quot; /&gt;
&lt;img src=&quot;/images/2023/20230517/IMG_0293.JPG&quot; alt=&quot;Session from Billy Hollis&quot; /&gt;
&lt;img src=&quot;/images/2023/20230517/IMG_0297.JPG&quot; alt=&quot;Selfie in front of the screen&quot; /&gt;
&lt;img src=&quot;/images/2023/20230517/IMG_0299.JPG&quot; alt=&quot;Musicians Hall of Fame&quot; /&gt;
&lt;img src=&quot;/images/2023/20230517/IMG_0305.JPG&quot; alt=&quot;Music tour in a bus&quot; /&gt;
&lt;img src=&quot;/images/2023/20230517/IMG_0313.JPG&quot; alt=&quot;Session from Veronika Kolesnikova&quot; /&gt;&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>How Copilot/AI helps me in my daily work</title>
			<link href="https://devopsjournal.io/blog/2023/04/24/how-Copilot-helps-me-in-my-daily-work"/>
			<updated>2023-04-24T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2023/04/24/how-Copilot-helps-me-in-my-daily-work</id>
			<content type="html">&lt;p&gt;During an innovation day at work, I needed to generate extra code and a new application. I wanted to check out the newly released &lt;a href=&quot;https://github.blog/2023-04-20-announcing-github-actions-deployment-protection-rules-now-in-public-beta/&quot;&gt;Deployment Protection Rules&lt;/a&gt; that can help you with protecting when a job in GitHub Actions can roll out your application to an environment.&lt;/p&gt;

&lt;p&gt;Deployment protection rules need a new GitHub App that can be triggered when an environment is targeted. That App can then run its own checks and report back the status to GitHub with a callback webhook.&lt;/p&gt;

&lt;p&gt;To get started I needed a new GitHub App that can receive the webhook from GitHub. Since I already have an &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/azure-functions/functions-overview?WT.mc_id=AZ-MVP-5003719&quot;&gt;Azure Function App&lt;/a&gt; up and running, I thought to add another HttpTrigger function to it, so I could log the payload and learn from that (the payload is not documented yet).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2023/20230421/craiyon_generating_code.png&quot; alt=&quot;AI generated code with the prompt&quot; /&gt;&lt;/p&gt;
&lt;h6 id=&quot;image-generated-with-craiyonai-using-the-following-prompt-sketched-photo-with-some-realism-of-an-ai-generating-code&quot;&gt;Image generated with &lt;a href=&quot;https://www.craiyon.com/&quot;&gt;Craiyon.ai&lt;/a&gt; using the following prompt: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sketched photo with some realism of an AI generating code&lt;/code&gt;.&lt;/h6&gt;

&lt;h1 id=&quot;usual-flow&quot;&gt;Usual flow&lt;/h1&gt;
&lt;p&gt;Normally I would start with searching for the things I need: some documentation for hosting an endpoint to receive the webhook payload, search for creating an app to handle the user login, etc. This can easily take days tinkering around to get things to where it is good enough for a POC. Instead, I used AI tools to help me, and had things up and running in a few hours.&lt;/p&gt;

&lt;h1 id=&quot;step-1-create-a-new-azure-function&quot;&gt;Step 1: Create a new Azure Function&lt;/h1&gt;
&lt;p&gt;I knew I needed an HttpTrigger and was about to search for it’s declaration, when I remembered I was already testing out GitHub Copilot for Chat, which is in &lt;a href=&quot;https://github.com/features/preview/copilot-x&quot;&gt;technical preview&lt;/a&gt; so I cannot say to much about it. I asked Copilot to write the function definition for me and sure it enough: it proposed the right code, including logging of the incoming payload. Using that code I could publish the function and configure it as a webhook from a new GitHub App.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2023/20230421/20230424_FunctionLog.png&quot; alt=&quot;Screenshot of the Azure function log, showing the deployment_callback_url&quot; /&gt;&lt;br /&gt;
The logs show that payload sends in the normal information you would expect, like the context for the trigger (in this case &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;workflow_dispatch&lt;/code&gt; was triggered by my user account) and the callback URL that can be used to report back the status of the check. The callback has the following form: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://api.github.com/repos/{OWNER}/{REPO}/actions/runs/{RUNID}/deployment_protection_rule&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That means it uses a well defined url, so I can just call that as the GitHub App for testing to see if it works. I’ve configured the Deployment Protection Rule with my new GitHub App based on an existing workflow (Copilot still helped here left and right), and tested it in a workflow to call the callback url.&lt;/p&gt;

&lt;h1 id=&quot;step-2-create-a-new-nodejs-app-for-user-configuration&quot;&gt;Step 2: Create a new node.js app for user configuration&lt;/h1&gt;
&lt;p&gt;An important part for my setup with the GitHub App will be to have the user configure settings for their environment. So that means I need to have a way to host an app that can do the OAuth flow with GitHub and store the settings in a database.&lt;/p&gt;

&lt;p&gt;I’m not a JavaScript developer who knows exactly how to get started, but I do have AI tools that can help me with that. The easiest option I have and that is free to use, is the &lt;a href=&quot;https://www.bing.com/new&quot;&gt;Bing integration in Edge&lt;/a&gt;. Here is my prompt: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;How can I get started with a simple webapplication based on node that uses GitHub authentication for the user to login and then retrieve information as that user&lt;/code&gt;:
&lt;img src=&quot;/images/2023/20230421/20230421_01_prompt.jpg&quot; alt=&quot;Screenshot of the prompt and the result&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Here is a part of the resulting app:&lt;br /&gt;
&lt;img src=&quot;/images/2023/20230421/20230421_04_app-definition.png&quot; alt=&quot;Screenshot of the npm code&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It even had the steps in there to store the configuration settings from the GitHub App in the .env file. I could just copy that example and add the code to the app.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2023/20230421/20230421_02_prompt.png&quot; alt=&quot;Screenshot of the .env setup and the login using the &apos;passport&apos; library&quot; /&gt;&lt;/p&gt;

&lt;p&gt;There was an error running the app locally, but I could just copy the error message and search for it following the same conversation on Bing. The first result was the right answer, so I could just copy that again (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm install express-session&lt;/code&gt;) and add it to the app.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2023/20230421/20230421_03_error.png&quot; alt=&quot;Example of the error&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You can see the working redirect for the user authentication below:
&lt;img src=&quot;/images/2023/20230421/20230421_05_login.png&quot; alt=&quot;Screenshot showing the authorization of the app using GitHub login for my user and App against my localhost:3000 app.&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;generated-code&quot;&gt;Generated code&lt;/h2&gt;
&lt;p&gt;The generated was not perfect: it picked up some things from the code it was trained on that can be improved. GitHub Copilot should prevent this kind of security issues when generating code, but I used Bing’s AI to generate this part of the code.&lt;/p&gt;

&lt;p&gt;This proves the point that even when using AI to help you generate code, setting up security pipelines is still very important. Luckily GitHub has made Advanced Security features available for free for public repos, allowing me to scan the code using CodeQL:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2023/20230421/20230424_CodeQL_results.png&quot; alt=&quot;Screenshot displaying a CodeQL alert on the cookie for the user being send back to the server in plain text&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If you want to learn more about GitHub Advanced Security, then check out my LinkedIn Learning Course on it: &lt;a href=&quot;/blog/2022/10/19/LinkedIn-Learning-GHAS&quot;&gt;LinkedIn Learning&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I could then use the generated code to get started, and improve it to make it more secure using the security tools available as well.&lt;/p&gt;

&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;And with that, most of my setup (the new Azure Function and the new node.js app) was done with me writing minimal amount of code, and still getting everything to work as I wanted.&lt;/p&gt;

&lt;p&gt;Do be aware that I have enough experience with these topics to both know what to look for, and how to spot / find / debug errors in my code. I’m sure that if I was a beginner, I would have had a lot more questions and would have needed to do a lot more research to get to the same result.&lt;/p&gt;

&lt;p&gt;I’m really excited to see what the future holds for Copilot and AI in general. I’m sure it will help me and many others to get more done in less time.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Enabling CodeQL on GitHub Enterprise Server</title>
			<link href="https://devopsjournal.io/blog/2023/02/07/Enabling-codeql-on-GitHub-Enterprise-Server"/>
			<updated>2023-02-07T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2023/02/07/Enabling-codeql-on-GitHub-Enterprise-Server</id>
			<content type="html">&lt;p&gt;To enable CodeQL on GitHub Enterprise Server you need to make sure you have GitHub Actions setup and running, including your own set of self-hosted runners. You can read more about that in my previous post &lt;a href=&quot;/blog/2022/10/09/Enabling-GitHub-Actions-on-Enterprise-Server&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;From that point you can get started to enable CodeQL. Of course, you’ll need to have it enabled in your license, and upload that license file to your server as well. Enabling starts at the appliance level, where you need to enable the code scanning and secret scanning features from the management console.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2023/20230207/barn-images-t5YUoHW6zRo-unsplash.jpg&quot; alt=&quot;Photo of all sort of tools hanging on a wall, like hammers, saws, etc.&quot; /&gt;&lt;/p&gt;
&lt;h4 id=&quot;photo-by-barn-images-on-unsplash&quot;&gt;Photo by &lt;a href=&quot;https://unsplash.com/@barnimages?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Barn Images&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/t5YUoHW6zRo?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;The default workflow that CodeQL will propose links to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github/codeql-action&lt;/code&gt; action, of which a static copy is installed with each Enterprise Server update. This organization is hidden by default, but you can navigate to it with the direct link. The issue here is that these action repos (github/dependabot-action and all repos in the actions org) only have the source code linked and updated with each Enterprise Server update. Since the CodeQL bundle is stored as a release asset, it is missing from the appliance. The bundle contains all CodeQL queries, including the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;security-extended&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;security-and-quality&lt;/code&gt; query types.&lt;/p&gt;

&lt;h2 id=&quot;syncing-the-codeql-bundle&quot;&gt;Syncing the CodeQL bundle&lt;/h2&gt;
&lt;p&gt;Normally we’d use the &lt;a href=&quot;https://github.com/actions/actions-sync&quot;&gt;actions-sync&lt;/a&gt; tool to update the actions on the appliance with the latest version on github.com. Unfortunately that tool does not sync any release assets. For syncing the release assets, we need to download the latest release of the &lt;a href=&quot;https://github.com/github/codeql-action-sync-tool&quot;&gt;codeql-action-sync-tool&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After downloading the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;codeql-action-sync-tool&lt;/code&gt;, you need to make sure you have write access to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github&lt;/code&gt; organization. By default you do not have it, so you cannot &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;write&lt;/code&gt; to the release assets or anything else in this org. That means we need to promote the user that will execute the syncing to an owner of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github&lt;/code&gt; org.&lt;/p&gt;

&lt;p&gt;To do so, you need to call the &lt;a href=&quot;https://docs.github.com/en/enterprise-server@3.4/admin/configuration/configuring-your-enterprise/command-line-utilities#ghe-org-admin-promote&quot;&gt;ghe-org-admin-promote&lt;/a&gt; command line utility from a remote shell on the appliance:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ghe-org-admin-promote &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; USERNAME &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; ORGANIZATION
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now we can call the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;codeql-action-sync-tool&lt;/code&gt; to download the latest version of the CodeQL bundle and upload it to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github/codeql-action&lt;/code&gt; repo. This tool will also update the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github/codeql-action&lt;/code&gt; repo with the latest version of the action code.&lt;br /&gt;
You can run it with this command:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;codeql-action-sync-tool &lt;span class=&quot;nb&quot;&gt;sync&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--force&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
 &lt;span class=&quot;nt&quot;&gt;--destination-url&lt;/span&gt; https://enterprise-server-url.com
 &lt;span class=&quot;nt&quot;&gt;--destination-token&lt;/span&gt; &amp;lt;PAT&amp;gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
 &lt;span class=&quot;nt&quot;&gt;--source-token&lt;/span&gt; &amp;lt;PAT&amp;gt; &lt;span class=&quot;c&quot;&gt;# prevents ratelimiting&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Of course, the machine that is running this will need to have access to github.com, as well as the Enterprise Server. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--source-token&lt;/code&gt; is optional, but it will prevent you from hitting the rate limit on github.com.&lt;/p&gt;

&lt;h1 id=&quot;running-codeql-efficiently&quot;&gt;Running CodeQL efficiently&lt;/h1&gt;
&lt;p&gt;Now that we have the CodeQL bundle on the appliance, we can start using it. The first thing you’ll notice is that the CodeQL bundle is quite large. The Linux zip file alone is 500Mb, which is quite a lot for a GitHub Action. The release asset appropriate for the OS of the runner is downloaded for each run. If you are using ephemeral runners (which you should!), this means that the CodeQL bundle is downloaded for each and every run. Given that this workflow is configured by default to run on every push, pull request as well as on a schedule (once a week), it can take up quite the bandwidth and thus hammer your appliance.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2023/20230207/codeql-action.png&quot; alt=&quot;Screenshot showing the different sizes of the release assets, with the Linux tar file being 500Mb&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The action follows the normal setup for GitHub Actions to check for the well known folder &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runner.tool_cache&lt;/code&gt;, which is stored in ‘/opt/hostedtoolcache/’. If it can find the bundle in that folder, it will use that. If it cannot find it, it will download the appropriate release asset.&lt;/p&gt;

&lt;p&gt;That means that we can prep our runners by copying the CodeQL bundle to this location. This will prevent the bundle from being downloaded for each run. The folder is used by including the CodeQL bundle release version and date: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/opt/hostedtoolcache/CodeQL/2.12.1-20230120/x64/codeql/codeql&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Priming that location will significantly lower the download pressure on your appliance, and speed up the execution of the CodeQL workflow.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Note: the version number has changed from calendar versioning to semantic versioning. The version number is now something like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v2.15.0&lt;/code&gt;, so be sure to handle that change as well.&lt;/p&gt;
&lt;/blockquote&gt;
</content>
		</entry>
	
		<entry>
			<title>Making the case for GitHub&apos;s Secret scanning</title>
			<link href="https://devopsjournal.io/blog/2023/01/22/Making-the-case-for-secret-scanning"/>
			<updated>2023-01-22T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2023/01/22/Making-the-case-for-secret-scanning</id>
			<content type="html">&lt;p&gt;After scanning the GitHub Actions Marketplace for the security of those actions (read that post &lt;a href=&quot;/blog/2022/09/18/Analysing-the-GitHub-marketplace&quot;&gt;here&lt;/a&gt;) I was curious to see what happens if I’d enable &lt;a href=&quot;https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning&quot;&gt;Secret Scanning&lt;/a&gt; on the forked repositories. I regularly teach classes on using GitHub Advanced Security (where secret scanning is part of) and I always tell my students that they should enable secret scanning on their repositories. I even have a &lt;a href=&quot;https://www.linkedin.com/learning/github-advanced-security/github-advanced-security?autoplay=true&quot;&gt;course on LinkedIn Learning about GitHub Advanced Security&lt;/a&gt; in case you want to learn more about it.&lt;/p&gt;

&lt;h2 id=&quot;what-is-github-secret-scanning&quot;&gt;What is GitHub Secret Scanning?&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning&quot;&gt;Secret scanning&lt;/a&gt; is part of the GitHub Advanced Security offering if you have a GitHub Enterprise account, but for public repos it is free to use (and enabled by default). GitHub scans the repository (full history) and issues and pull requests contents to see if it detects a secret. The detection happens based on a set of regular expressions shared by GitHub’s secret scanning partners (over 100 and counting). If a secret is detected, GitHub will notify the repository owner as well as the secret scanning partner. Depending on the context (public repo or not for example), the secret scanning partner can decide to revoke the secret immediately, which I think most partners do.&lt;/p&gt;

&lt;p&gt;This functionality is so good and fast, that I routinely post my GitHub Personal Access Tokens to GitHub issues during my trainings, to show the power of secret scanning. Usually I already have an email and a revoked token before I finish explaining what is happening in the background.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2023/20230122/kristina-flour-BcjdbyKWquw-unsplash.jpg&quot; alt=&quot;Photo of a woman holding her index finger to her mouth in a &apos;sst&apos; manner&quot; /&gt;&lt;/p&gt;
&lt;h4 id=&quot;photo-by-kristina-flour-on-unsplash&quot;&gt;Photo by &lt;a href=&quot;https://unsplash.com/@tinaflour?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Kristina Flour&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/BcjdbyKWquw?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;.&lt;/h4&gt;

&lt;h2 id=&quot;analyzing-the-actions-repositories&quot;&gt;Analyzing the actions repositories.&lt;/h2&gt;
&lt;p&gt;In my GitHub Actions marketplace scan, I have the repositories of 14k actions forked into an organization, so I can enable secret scanning and see what I get back from secret scanning. Since all Action repositories on the marketplace are public, any secret that is found has been found before, so I expect all these secrets to already have been revoked before.&lt;/p&gt;

&lt;p&gt;Overall results: Found [1353] secrets for the organization in [1110] repositories (out of 13954 repos scanned). Here is a top 15 of most found secrets to see what is being found:&lt;/p&gt;

&lt;h2 id=&quot;secret-scanning-alerts&quot;&gt;Secret scanning alerts&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Alert type&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Count&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;GitHub App Installation Access Token&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;692&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Azure Storage Account Access Key&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;155&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;GitHub Personal Access Token&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;120&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Amazon AWS Secret Access Key&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;50&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Plivo Auth ID&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;40&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Amazon AWS Access Key ID&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;40&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Google API Key&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;34&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Slack API Token&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;31&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Slack Incoming Webhook URL&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;27&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Atlassian API Token&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;22&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Plivo Auth Token&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;16&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;GitHub SSH Private Key&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;12&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Amazon AWS Session Token&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;12&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;HashiCorp Vault Service Token&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;11&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;PyPI API Token&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;10&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;With all the news recently about credential leaking and malicious actors using these secrets to do bad things, I think it is very important to enable secret scanning on your repositories! Having this data really shows the power of GitHub and its secret scanning partners.&lt;/p&gt;

&lt;h2 id=&quot;analyzing-the-results&quot;&gt;Analyzing the results&lt;/h2&gt;
&lt;p&gt;I wanted to get these results to get a feel for the amount of things secret scanning would find. I my opinion, the maintainers of these actions have a high level of understanding Git and GitHub, so you’d expect a relative low number of secrets being found. Still, 1110 repositories out of 13954 repos is 7.9% off all repos where secrets have been found. This shows how easy it is to accidentally commit secrets to a repository. Even in my own repos, a secret was found (a GitHub Personal Access Token even) that I accidentally committed in an environment file! And that while I teach people on these things!&lt;/p&gt;

&lt;p&gt;I think this is a good number to show to my students and customers to make the case for enabling secret scanning on their repositories. Even folks with a high level of understanding, will still make mistakes and &lt;a href=&quot;https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning&quot;&gt;secret scanning&lt;/a&gt; will help by finding them for you, every time you make a change to your repository.&lt;/p&gt;

</content>
		</entry>
	
		<entry>
			<title>Improving your GitHub repositories security setup by adding the OSSF scorecard action</title>
			<link href="https://devopsjournal.io/blog/2022/12/08/Adding-OSSF-scorecard-action-to-your-repo"/>
			<updated>2022-12-08T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2022/12/08/Adding-OSSF-scorecard-action-to-your-repo</id>
			<content type="html">&lt;p&gt;Recently I’ve started to add the &lt;a href=&quot;https://github.com/ossf/scorecard-action&quot;&gt;OSSF scorecard action&lt;/a&gt; to my (action) repositories. This is a GitHub action that will run the &lt;a href=&quot;https://github.com/ossf/scorecard&quot;&gt;OSSF scorecard&lt;/a&gt; checks against your repository to see if you are following best practices, like having a security policy, using a code scanning tool, etc. Using this badge can give your users a quick overview of the security of your repository. OSSF stands for ‘Open Source Security Foundation’ and is an initiative to improve the security of open source repositories.&lt;/p&gt;

&lt;h2 id=&quot;scorecard-action&quot;&gt;Scorecard action&lt;/h2&gt;
&lt;p&gt;The scorecard action will do three things (if you use the default settings):&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Analyze your repository for the checks&lt;/li&gt;
  &lt;li&gt;Upload the results to the GitHub Security tab (using a SARIF file)&lt;/li&gt;
  &lt;li&gt;Upload the results to the OSSF API (A web API that you can even host yourself)
With the data uploaded to the OSSF API you can then retrieve a badge with your latest score and show that on your repository:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;/images/2022/20221208/20221208_00_OSSF_badge.png&quot; alt=&quot;Screenshot of the OSSF badge in a repo, showing a score of 7.1&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;scoring&quot;&gt;Scoring&lt;/h2&gt;
&lt;p&gt;The action checks your repository against &lt;a href=&quot;https://github.com/ossf/scorecard#scorecard-checks&quot;&gt;several checks&lt;/a&gt; and then calculates a score for each check on a scale of 0 to 10. Based on the risk for that check, the score is then multiplied with a weight. The final score is the average of all the weighted scores. The higher the score, the better.&lt;/p&gt;

&lt;p&gt;Some of the checks that are executed are:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Branch-Protection: Is branch protection enabled for the default branch?&lt;/li&gt;
  &lt;li&gt;Are GitHub Action versions pinned to SHA hashes, following &lt;a href=&quot;/blog/2021/02/06/GitHub-Actions&quot;&gt;best practices&lt;/a&gt;?&lt;/li&gt;
  &lt;li&gt;Is the repository using a code scanning tool? Note that this check currently only supports CodeQL and SonarCloud&lt;/li&gt;
  &lt;li&gt;Is there a security policy defined?&lt;/li&gt;
  &lt;li&gt;Is there a definition file for Dependabot?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;setting-it-up&quot;&gt;Setting it up&lt;/h2&gt;
&lt;p&gt;Setting is up is as easy as going to the &lt;a href=&quot;https://github.com/ossf/scorecard-action#workflow-example&quot;&gt;OSSF scorecard action&lt;/a&gt; repo and copy the workflow example. Add that to you a new workflow file in your repo and run it from the default branch (usually &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt;). After the first run, you can add a link to your README.md file to show the badge.&lt;/p&gt;

&lt;p&gt;The result will be a badge showing the latest score:
&lt;a href=&quot;https://api.securityscorecards.dev/projects/github.com/devops-actions/load-runner-info&quot;&gt;&lt;img src=&quot;https://api.securityscorecards.dev/projects/github.com/devops-actions/load-runner-info/badge&quot; alt=&quot;OpenSSF Scorecard&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;code-scanning-alerts&quot;&gt;Code scanning alerts&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/ossf/scorecard-action#workflow-example&quot;&gt;default workflow&lt;/a&gt; also uploads the check results as a SARIF file to the Code Scanning Alerts that you can find on the Security tab of your repository. Each check can give one or more results, so you will get an alert for each result. That way you can fix the issues one by one.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2022/20221208/20221208_01_Code_scanning_alert.png&quot; alt=&quot;Screenshot of an alert about dependency pinning that is not followed&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Even better is that most of these alerts will give you actionable suggestions on how to fix the issue. For example, the alert above will give you a link to the &lt;a href=&quot;https://app.stepsecurity.io/securerepo/&quot;&gt;Step Security&lt;/a&gt; Application that can analyze your repository and give you a pull request with the changes to fix the issue. They focus on three things:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Restrict permission for the GITHUB_TOKEN&lt;/li&gt;
  &lt;li&gt;Add security agent for GitHub hosted runner (Ubuntu only)&lt;/li&gt;
  &lt;li&gt;Pin actions to a full SHA hash&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/images/2022/20221208/20221208_02_ImproveWorkflow.png&quot; alt=&quot;Screenshot of the three options Step Security gives you to improve your workflow&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;restrict-permission-for-the-github_token&quot;&gt;Restrict permission for the GITHUB_TOKEN&lt;/h3&gt;
&lt;p&gt;One of the best practices is to always indicate what permissions you want to allow for the GitHub token that will be used in your workflow. The default setting is to permissive: it is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;read&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;write&lt;/code&gt; for everything in your repo. I’ve been advocating that people always make this read-only at the organization level.&lt;/p&gt;

&lt;p&gt;Since you cannot see what the organization permissions are, it’s a best practice to always specify the rights. You can add it to the top of your workflow file:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;permissions&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;read-all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And now you can override it at the job level if you need to. For example, if you need to push to a branch, you can add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;write&lt;/code&gt; to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;contents&lt;/code&gt; permission:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;permissions&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;contents&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;write&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
     &lt;span class=&quot;c1&quot;&gt;# etc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;add-security-agent-for-github-hosted-runner-ubuntu-only&quot;&gt;Add security agent for GitHub hosted runner (Ubuntu only)&lt;/h3&gt;
&lt;p&gt;This setting is something I want to look into more. It seems that you can add a security agent to your GitHub hosted runner (available only for Ubuntu). It’s an extra product from &lt;a href=&quot;https://github.com/step-security/harden-runner&quot;&gt;Step Security&lt;/a&gt; which is available for free. It will analyze the network traffic in the job and log that in their system. After a couple of runs they know what kind of traffic is to be expected, and then you can convert that to basically a firewall rule. Any violations will then be logged and blocked. This can give you a better perimeter defense (as by default all traffic is allowed).&lt;/p&gt;

&lt;p&gt;Definitely something to look into more!&lt;/p&gt;

&lt;h3 id=&quot;pin-actions-to-a-full-sha-hash&quot;&gt;Pin actions to a full SHA hash&lt;/h3&gt;
&lt;p&gt;The last option is to pin your actions to a full SHA hash. This is a best practice that I’ve been &lt;a href=&quot;/blog/2021/2021/02/06/GitHub-Actions&quot;&gt;advocating for a while&lt;/a&gt;. It’s a bit more work, but it will make sure that your workflow will not be affected by a malicious actor pushing a new version of an action. What I really like is that this setting will analyze your workflow file (either pasted in or just all workflows in your public repo) and then suggest the SHA hashes of the actions, by looking at their latest version! Even better, they store the latest version in the comment next to the SHA hash, so you can understand where the hash comes from. Even Dependabot understands that comment and will update it in their version update PRs!&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Examples for calling the GitHub GraphQL API (with ProjectsV2)</title>
			<link href="https://devopsjournal.io/blog/2022/11/28/github-graphql-queries"/>
			<updated>2022-11-28T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2022/11/28/github-graphql-queries</id>
			<content type="html">&lt;p&gt;Recently we had to call the GitHub GraphQL API for creating a new GitHub Project (with V2). Since this can only be done with the GraphQL API, we had to figure out how to do this. We found little bits and pieces of information, but no complete example. So we decided to write one ourselves. I hope this helps you as well.&lt;/p&gt;

&lt;h2 id=&quot;projectsv2-github-graphql-api&quot;&gt;ProjectsV2 GitHub GraphQL API&lt;/h2&gt;
&lt;p&gt;The new GitHub Projects simply do not have a REST API at the moment, so you are forced to use the GraphQL API. As an extra challenge, the GraphQL API requires a &lt;a href=&quot;https://docs.github.com/en/graphql/guides/migrating-graphql-global-node-ids&quot;&gt;new set of ID’s for these calls&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;logging-in&quot;&gt;Logging in&lt;/h2&gt;
&lt;p&gt;If you are not careful, then the first hurdle will be to login with the right scopes. I’m going to use the GitHub CLI in this post, but you will need to make sure you have the right scopes for this with manual API calls as well. Logging in with just the normal &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gh auth login&lt;/code&gt; call is not enough in this case. Add the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;project&lt;/code&gt; scopes to your call:&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;gh&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;auth&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;login&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--scopes&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;project&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;getting-the-new-ids&quot;&gt;Getting the new ID’s&lt;/h2&gt;
&lt;p&gt;Now we can start making the calls to the GraphQL API. Since this API with ProjectsV2 needs the new ID’s, make all calls like this to get the new ID’s:&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# powershell:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$response&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gh&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;api&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;graphql&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;X-Github-Next-Global-ID: 1&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-F&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$query&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ConvertFrom-Json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now we can call the API with the current login to get the new ID for this login:&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$query&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;query { viewer { login, id } }&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$response&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gh&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;api&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;graphql&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;X-Github-Next-Global-ID: 1&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-F&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$query&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ConvertFrom-Json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Need to get the ID from an organization? Use this query:&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$query&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;query { organization(login: &quot;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$organizationName&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&quot;) { id, name } }&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;working-with-projectsv2&quot;&gt;Working with ProjectsV2&lt;/h2&gt;
&lt;p&gt;Now we can start loading the existing projects for an organization:&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$query&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;query { organization(login: &quot;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$organizationName&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&quot;) { projectsV2(first: 100) { edges { node { id } } } } }&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And we can create a new project:&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$query&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;mutation (&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;`$&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;ownerId: ID!, &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;`$&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;title: String!) { createProjectV2(input: { ownerId: &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;`$&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;ownerId, title: &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;`$&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;title }) { projectV2 { id } } }&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;    
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you want to see the full script, you can find it &lt;a href=&quot;https://github.com/rajbos/github-graphql-examples&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Working with GitHub secrets without admin rights</title>
			<link href="https://devopsjournal.io/blog/2022/11/02/GitHub-secrets-without-admin-rights"/>
			<updated>2022-11-02T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2022/11/02/GitHub-secrets-without-admin-rights</id>
			<content type="html">&lt;p&gt;I was giving a training today on &lt;a href=&quot;https://github.com/features/actions&quot;&gt;GitHub Actions&lt;/a&gt; and learned something new! One of the attendees asked about being able to read and write to Repository Secrets without having admin rights. I had never tried this before, but it turns out it is possible!&lt;/p&gt;

&lt;h2 id=&quot;the-premise&quot;&gt;The premise:&lt;/h2&gt;
&lt;p&gt;To be able to create actions on the repository you need to have Admin access to the repository: otherwise the UI will not be visible, since it is under the repository settings. For organization level secrets you need Admin access to the &lt;em&gt;organization&lt;/em&gt; level.&lt;/p&gt;

&lt;p&gt;That also means that team members that do not have Admin access, cannot SEE the repository secrets in the UI. They always had to rely on the examples in other workflow files to see what type of secrets were available. Today I learned that any user with &lt;strong&gt;write&lt;/strong&gt; access to the repository can also create, update, and delete a secret. Even if they cannot see it in the UI, they can use the API to manage the secrets. It would be great if GitHub updates the UI to make these kinds of information available to all users that need it (write and maintain). Something similar happens with available GitHub Self-hosted Runners (information is not available for non-admins).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2022/20221102/markus-winkler-wpOa2i3MUrY-unsplash.jpg&quot; alt=&quot;Photo of a smart phone with the thinking emoji displayed on it&quot; /&gt;&lt;/p&gt;
&lt;h5 id=&quot;photo-by-markus-winkler-on-unsplash&quot;&gt;Photo by &lt;a href=&quot;https://unsplash.com/@markuswinkler?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Markus Winkler&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/s/photos/think?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/h5&gt;

&lt;h2 id=&quot;the-solution&quot;&gt;The solution:&lt;/h2&gt;
&lt;p&gt;If you have &lt;strong&gt;write&lt;/strong&gt; access to the repository, you can use the API to create, update, and delete secrets. You can use the &lt;a href=&quot;https://cli.github.com/&quot;&gt;GitHub CLI&lt;/a&gt; to do this, or you can use the &lt;a href=&quot;https://docs.github.com/en/rest/actions/secrets#list-repository-secrets&quot;&gt;GitHub REST API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There is also an API for Environment secrets that work the same way: &lt;a href=&quot;https://docs.github.com/en/rest/actions/secrets#list-environment-secrets&quot;&gt;GitHub REST API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The GitHub CLI is a great tool to use, since it is easy to use and it is cross-platform. You can install it on Windows, Linux, and Mac. 
For maintaining the repository secrets, there is a native call in the CLI that you can use: &lt;a href=&quot;https://cli.github.com/manual/gh_secret_list&quot;&gt;link&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;c&quot;&gt;# list all secrets for the current repo&lt;/span&gt;
  gh secret list
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For environment secrets there is no native call in the CLI, but you can use the REST API to list the secrets. You can use the following command to list the environment secrets:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;c&quot;&gt;# list the environments on the repository:&lt;/span&gt;
    gh api repos/&amp;lt;OWNER&amp;gt;/&amp;lt;REPO&amp;gt;/environments

    &lt;span class=&quot;c&quot;&gt;# list secrets for that environment:&lt;/span&gt;
    gh api repos/&amp;lt;OWNER&amp;gt;/&amp;lt;REPO&amp;gt;/environments/&amp;lt;ENVIRONMENT&amp;gt;/secrets
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Output of the CLI calls:&lt;br /&gt;
&lt;img src=&quot;/images/2022/20221102/20221102_Secret_Listing.png&quot; alt=&quot;Screenshot of the output of the CLI calls&quot; /&gt;&lt;/p&gt;

&lt;p&gt;From then you get options to set (create), delete or update the value of the secret. Retrieving the value of the secret is not possible, since there are no API calls for that, which makes sense since they are &lt;em&gt;secrets&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Examples of setting a variable:&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Paste secret value for the current repository in an interactive prompt&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;gh secret &lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;MYSECRET

&lt;span class=&quot;c&quot;&gt;# Read secret value from an environment variable&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;gh secret &lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;MYSECRET &lt;span class=&quot;nt&quot;&gt;--body&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$ENV_VALUE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Read secret value from a file&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;gh secret &lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;MYSECRET &amp;lt; myfile.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;figuring-out-environments-and-secrets&quot;&gt;Figuring out environments and secrets&lt;/h2&gt;
&lt;p&gt;The environment secrets &lt;em&gt;can&lt;/em&gt; be listed by users with write access, and they can &lt;em&gt;create&lt;/em&gt; environments directly with the API.&lt;/p&gt;

&lt;p&gt;For creating an environment you need to have write access to an repo, and the repo needs to be in an Organization. User space repos do not work with this write API.&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;c&quot;&gt;# list secrets for that environment:&lt;/span&gt;
    gh api repos/&amp;lt;OWNER&amp;gt;/&amp;lt;REPO&amp;gt;/environments/&amp;lt;ENVIRONMENT&amp;gt;/secrets
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For creating environments in your own user space, you have Admin access so you can use this API call. If you add a body you can the timeout rule, specify protection rules (who needs to approve the job that targets the environment) as well as the deployment branch rule (which branch is allowed to target that environment).&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; gh api &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; PUT /repos/&amp;lt;OWNER&amp;gt;/&amp;lt;REPO&amp;gt;/environments/NEW_ENVIRONMENT
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Results:&lt;br /&gt;
&lt;img src=&quot;/images/2022/20221102/20221102_CreateEnvironment.png&quot; alt=&quot;Screenshot of the output of &apos;gh api -X PUT&apos; call to create the environment&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If you are invited as a Collaborator to another user’s repo, you cannot create environments with the API. Trying to make the call you can get two results:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;404: Not found (repo is private)&lt;/li&gt;
  &lt;li&gt;403: Forbidden (public repo, but you do not have admin access)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As long as you do have &lt;strong&gt;read&lt;/strong&gt; access to the repo, you can list the environments:&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;c&quot;&gt;# list the environments on the repository:&lt;/span&gt;
    gh api repos/&amp;lt;OWNER&amp;gt;/&amp;lt;REPO&amp;gt;/environments
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;💡 As you have read access to all &lt;em&gt;public repos&lt;/em&gt;, you can list the environments on any public repo as well. Since this information is already available from the workflow files (and they are public), having the name of the environment is not a big deal. Unfortunately the API returns all there is to know about that environment, including the deployment branch and protection rules. The feedback I got from GitHub is that this is ‘by design’. You can get similar information from the &lt;a href=&quot;https://docs.github.com/en/rest/branches/branches#list-branches&quot;&gt;branches API&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;closing-thoughts&quot;&gt;Closing thoughts:&lt;/h2&gt;
&lt;p&gt;The only thing that is not available this way is retrieving a list of Organization level secrets: the API calls for that need &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;admin:org&lt;/code&gt; scope, which a user with write access to the repository does not have by default. That is still a big missing feature, since these kinds of secrets are often used to set some large scope (often read-only) secrets for the entire organization, or to have 1 ‘team’ secret that gets added to the repos that team owns.&lt;/p&gt;

&lt;p&gt;So, to list all the Repo secrets you can run:&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gh secret list
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And for finding the Environments and their secrets:&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;c&quot;&gt;# list the environments on the repository:&lt;/span&gt;
    gh api repos/&amp;lt;OWNER&amp;gt;/&amp;lt;REPO&amp;gt;/environments

    &lt;span class=&quot;c&quot;&gt;# list secrets for that environment:&lt;/span&gt;
    gh api repos/&amp;lt;OWNER&amp;gt;/&amp;lt;REPO&amp;gt;/environments/&amp;lt;ENVIRONMENT&amp;gt;/secrets
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
		</entry>
	
		<entry>
			<title>LinkedIn Learning: GitHub Advanced Security</title>
			<link href="https://devopsjournal.io/blog/2022/10/19/LinkedIn-Learning-GHAS"/>
			<updated>2022-10-19T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2022/10/19/LinkedIn-Learning-GHAS</id>
			<content type="html">&lt;p&gt;My LinkedIn Learning course on GitHub Advanced Security (GHAS) has been released!
In this course I teach you all about the features of Advanced Security:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Dependabot&lt;/li&gt;
  &lt;li&gt;Code scanning&lt;/li&gt;
  &lt;li&gt;Secret scanning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can watch it with a LinkedIn Learning account (30 day trial is available) with this link: &lt;a href=&quot;https://www.linkedin.com/learning/github-advanced-security/github-advanced-security?autoplay=true&quot;&gt;GHAS on LinkedIn Learning&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id=&quot;teaser-for-the-training&quot;&gt;Teaser for the training:&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://youtu.be/HTfi5NnZbmA&quot;&gt;&lt;img src=&quot;/images/2022/20221019/20221019_LIL_Teaser_Still.jpeg&quot; alt=&quot;Link to the teaser video&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>How GitHub Actions versioning system works</title>
			<link href="https://devopsjournal.io/blog/2022/10/19/How-GitHub-Actions-versioning-works"/>
			<updated>2022-10-19T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2022/10/19/How-GitHub-Actions-versioning-works</id>
			<content type="html">&lt;h2 id=&quot;tldr&quot;&gt;TL;DR&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;The runner just downloads what you specified, by getting it from the tag&lt;/li&gt;
  &lt;li&gt;The runner does not do SemVer at all. It’s up to the maintainer&lt;/li&gt;
  &lt;li&gt;Even GitHub does not update (or create) all SemVer versions, so @v3 is not necessarily the latest thing for v3!&lt;/li&gt;
  &lt;li&gt;The marketplace shows releases, not tags. If the maintainer does not actually release, it’s not visible&lt;/li&gt;
  &lt;li&gt;It’s more secure to use a SHA hash instead of a tag: read more info &lt;a href=&quot;/blog/2021/12/11/GitHub-Actions-Maturity-Levels&quot;&gt;here&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;semantic-versioning&quot;&gt;Semantic versioning&lt;/h2&gt;

&lt;p&gt;When using GitHub Actions, the default is to use the Semantic Versions for which the actions where released. Semantic versioning (SemVer) is an industry wide standard of giving meaning to the version number. SemVer always follows this setup:&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;MAJOR.MINOR.PATCH&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Given a version number you increment the:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;MAJOR version when you make incompatible API changes&lt;/li&gt;
  &lt;li&gt;MINOR version when you add functionality in a backwards compatible manner&lt;/li&gt;
  &lt;li&gt;PATCH version when you make backwards compatible bug fixes
Optionally you can use any suffix label you want to indicate special versions like alpha, beta, release candidate, etc:&lt;/li&gt;
  &lt;li&gt;1.2.5-alpha.1&lt;/li&gt;
  &lt;li&gt;1.2.5-RC.1&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal of using SemVer is that you can then specify what you as a user of a library want to use. You can be very specific and say you want to use version &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;9.1.4&lt;/code&gt;, but also be less specific and say you want to use version &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;9.1&lt;/code&gt;. Following SemVer this means you want to use any version that matches the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;9.1&lt;/code&gt; MAYOR.MINOR version. This means you will get the latest version of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;9.1&lt;/code&gt; that is available. This is very useful when you want to use the latest version of a library, but you don’t want to have to update your code every time a new version is released. You can just specify the MAYOR.MINOR version and you will get the latest version of that version. Effectively that means that you’re saying &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;9.1.*&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If version 9.1.3 is the current latest PATCH version, your package manager will download that version. If version 9.1.4 is released, your package manager will download that version. If version 9.2.0 is released, your package manager will &lt;em&gt;not&lt;/em&gt; download that version, because it does not match the MAYOR.MINOR version you specified.&lt;/p&gt;

&lt;p&gt;The same setup goes for the MINOR version. If you specify version &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;9&lt;/code&gt;, you will get the latest version of that MAYOR version. This means that you will get the latest version of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;9.1&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;9.2&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;9.3&lt;/code&gt; and so on. Effectively that means that you’re saying &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;9.*.*&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;what-the-runner-does-with-semantic-versioning-of-using-github-actions&quot;&gt;What the runner does with semantic versioning of using GitHub Actions&lt;/h2&gt;
&lt;p&gt;The runner that executes GitHub Actions for us is open source. You can check the source code for it &lt;a href=&quot;https://github.com/actions/runner&quot;&gt;here&lt;/a&gt;. If you dive into it, you can find that the runner tries to download the version from the Action you specified:&lt;/p&gt;
&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v2     --&amp;gt; will download the repo with TAG = v2&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v3.1.0 --&amp;gt; will download the repo with TAG = v3.1.0&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@main   --&amp;gt; will download the repo with BRANCH = main&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@e2f20e631ae6d7dd3b768f56a5d2af784dd54791 --&amp;gt; will download the repo with COMMIT = e2f20e631ae6d7dd3b768f56a5d2af784dd54791 (SHA hash)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;You can find the code with it makes the download link &lt;a href=&quot;https://github.com/actions/runner/blob/5421fe3f7107f770c904ed4c7e506ae7a5cde2c2/src/Runner.Worker/ActionManager.cs#L1122-L1123&quot;&gt;here&lt;/a&gt;. The runner then calls the REST API to download a &lt;a href=&quot;https://docs.github.com/en/rest/repos/contents#download-a-repository-archive-tar&quot;&gt;tarball of the repo&lt;/a&gt; (or on a non-Windows host the zipfile of the repo).&lt;/p&gt;

&lt;p&gt;If the TAG (or branch or SHA hash) you have specified is not available in the repo, the runner will give an error saying it cannot find that version.&lt;/p&gt;

&lt;h2 id=&quot;using-semver-for-actions&quot;&gt;Using SemVer for Actions&lt;/h2&gt;
&lt;p&gt;Now the interesting thing is what happens if you specify a semantic versioning pattern for an action. For example:&lt;/p&gt;
&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v3.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Following semantic versioning, you’d expect that this example would work. Configuring actions with a version (or tag, or hash) is required these days so the runner can find the correct reference to download. That means that the maintainer of the action MUST follow SemVer when releasing their actions, as also described in the &lt;a href=&quot;https://docs.github.com/en/actions/creating-actions/releasing-and-maintaining-actions#setting-up-github-actions-workflows&quot;&gt;GitHub Actions documentation&lt;/a&gt;. If the maintainer does not follow SemVer, the runner will not be able to find the correct version to download.&lt;/p&gt;

&lt;p&gt;Now guess again what happens when you specify version &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v3.1&lt;/code&gt; for the checkout action. The runner will try to download the repo with TAG = &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v3.1&lt;/code&gt;. But that tag does not exist! The runner will then give an error saying it cannot find that version. So the runner does not check for matching versions by itself!&lt;/p&gt;
&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v3.1 &amp;lt;-- this will fail!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;You can find the tags for this action &lt;a href=&quot;https://github.com/actions/checkout/tags&quot;&gt;here&lt;/a&gt; and see that v3.1 is missing! So even the most used action, does not follow GitHub’s best practices!!!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2022/20220918/this-is-fine.jpg&quot; alt=&quot;Image of the &apos;this is fine&apos; meme&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Even better, the &lt;a href=&quot;https://github.com/marketplace&quot;&gt;GitHub Actions Marketplace&lt;/a&gt; actually does not show the version from the tags: it only shows the information from Releases in the repo! That means you could be missing out on tags that where not released as a GitHub Release.&lt;/p&gt;

&lt;p&gt;Check the tag list versus the release list shown in the marketplace:
&lt;img src=&quot;/images/2022/20221019/20221019_actions_checkout.png&quot; alt=&quot;Tag v3 is missing in the marketplace&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;make-your-github-actions-usage-more-secure&quot;&gt;Make your GitHub Actions usage more secure&lt;/h1&gt;
&lt;p&gt;I’ve been telling people that tags are not secure: the maintainer of the action can update the tag to point to a different commit. That means that your workflow could be using a commit you verified (that should always be step 1!), but all of a sudden the maintainer of the action updates the tag to point to a different commit. That means that your workflow is now using different code, which you did not verify! Read more on becoming more secure with your Actions usage in &lt;a href=&quot;/blog/2021/12/11/GitHub-Actions-Maturity-Levels&quot;&gt;this blogpost&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The way to fix this and make your setup more secure, is to use the SHA hash of the commit you want to use. That way you can verify the code yourself and you know that the code you’re using is the code you verified. Incoming changes can be send as notifications by setting up Dependabot for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github-actions&lt;/code&gt; ecosystem.&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;We keep telling people to follow SemVer for a reason, but for GitHub Actions the honus is on the maintainer of the action to actually re-tag all matching versions of their action with the correct SemVer version. And apparently, even GitHub doesn’t follow along with this.&lt;/p&gt;

&lt;p&gt;So when you have a current release &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v4.2.5&lt;/code&gt;, you also need to re-tag that commit with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v4.2&lt;/code&gt; tag, as well as the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v4&lt;/code&gt; tag. This way the runner can find the correct version to download.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;
&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;job1&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# works, as this is an actual tag in the repo&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v3&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;job2&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# works, as this is an actual tag in the repo&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v3.1.0&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;job3&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# does not work, as this is NOT an actual tag in the repo&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v3.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
		</entry>
	
		<entry>
			<title>Enabling GitHub Actions on Enterprise Server: Common gotcha&apos;s</title>
			<link href="https://devopsjournal.io/blog/2022/10/08/Enabling-GitHub-Actions-on-Enterprise-Server"/>
			<updated>2022-10-08T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2022/10/08/Enabling-GitHub-Actions-on-Enterprise-Server</id>
			<content type="html">&lt;p&gt;When customers start using GitHub Enterprise with Actions and private runners, there are some common gotcha’s you can run into. In this post I’m sharing the ones I have encountered so far. Even Dependabot comes along, since that runs on Actions as well for GitHub Enterprise Server.&lt;/p&gt;

&lt;p&gt;List of topics:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;First of all: &lt;a href=&quot;/blog/2021/05/16/Dont-use-self-signed-certificates-on-GitHub-Enterprise&quot;&gt;Don’t use self signed certificates on GitHub Enterprise&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;The default actions in will download the binaries from github.com&lt;/li&gt;
  &lt;li&gt;Actions org will be cleaned up with each major/minor update&lt;/li&gt;
  &lt;li&gt;Mismatches in the settings UI (harder for admins)&lt;/li&gt;
  &lt;li&gt;Dependabot runners need to be created&lt;/li&gt;
  &lt;li&gt;Log storage is external to the server&lt;/li&gt;
  &lt;li&gt;GitHub Connect is mandatory&lt;/li&gt;
  &lt;li&gt;Think about GitHub Actions and using them securely&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/images/2022/20221008/Dependabot-Actions.png&quot; alt=&quot;Image with the logos of Dependabot and GitHub Actions&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;the-default-actions-in-the-actions-organization-will-download-the-binaries-from-githubcom&quot;&gt;The default actions in the /actions organization, will download the binaries from github.com&lt;/h1&gt;
&lt;p&gt;Be aware that the default actions in the /actions organization (setup-node, setup-go, etc.), will download the binaries they need from &lt;a href=&quot;https://github.com/actions/setup-node/blob/main/src/installer.ts#L140&quot;&gt;github.com&lt;/a&gt;. This means the runner will download those files without any authentication and will be rate limited after 60 downloads/hour/ip-address.
To get around that, you will need to create your own version of these actions and download the releases from something like GitHub Releases or an internal source like Artifactory, or store them as GitHub Releases in a repository.&lt;/p&gt;

&lt;h1 id=&quot;actions-org-will-be-cleaned-up-with-each-majorminor-update&quot;&gt;Actions org will be cleaned up with each major/minor update&lt;/h1&gt;
&lt;p&gt;This was not such a pleasant surprise: we have had to customize the default actions, since setup-node and setup-go will download the binaries from github.com (see above). Our runners do not have internet access for security reasons. We have created our own versions of these actions that download the binaries from our internal Artifactory instance and uploaded them to our GitHub Enterprise instance. This worked fine for a while, but when we upgraded from 3.1 to 3.2, the actions org was cleaned up and our custom actions were gone. This was not a problem for us, since we had copies of the source code on several machines, but it was a surprise. So be aware: the entire org gets cleaned up with each major/minor update!&lt;/p&gt;

&lt;h1 id=&quot;mismatches-in-settings-ui-its-all-over-the-place&quot;&gt;Mismatches in settings UI: it’s all over the place&lt;/h1&gt;
&lt;p&gt;There are 2 different idioms in the settings, and if you have admin rights (either Enterprise, Org or repo level), you will see both.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;UI where you make changes and need to scroll down to save them&lt;/li&gt;
  &lt;li&gt;UI where any changes trigger a post back to the server and are stored (and in effect immediately)!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For the second one, here is an example. There is a save button in here, so…. what do you thing will happen if you change the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;All repositories&lt;/code&gt; setting in the drop down?&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2022/20221008/20221008_Settings.png&quot; alt=&quot;Screenshot of the &apos;General actions permissions&apos; on the organization level&quot; /&gt;
This setting posts back to the server immediately, so the change is stored and in effect immediately! There is a small page reload, but if you are not paying attention, you could miss it. Guess how I turned off GitHub Actions for all users in our Production Environment? I took a bit more then half an hour before users started calling us (my team maintains it), and a check on this page to learn that the setting was changed…. That was not the intention, because I didn’t press the save button!&lt;/p&gt;

&lt;h1 id=&quot;dependabot-runners-need-to-be-created&quot;&gt;Dependabot runners need to be created&lt;/h1&gt;
&lt;p&gt;When you want to enable Dependabot, and it’s version updates, you need to do this at several levels: first enable it on the appliance. Then change the settings for the Organization level to allow it to be used.&lt;/p&gt;

&lt;p&gt;Then you can configure it per repo:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Enable the Software Composition Analysis (SCA) feature (that’s Dependabot starting point)&lt;/li&gt;
  &lt;li&gt;Configure the Dependabot.yml file to run with updates&lt;/li&gt;
  &lt;li&gt;Be aware that Dependabot runs on actions and uses the default setup action to configure the ecosystem (more below)&lt;/li&gt;
  &lt;li&gt;Notice that the Dependabot runs use a specific label to target runners with (more below)&lt;/li&gt;
  &lt;li&gt;Create runners for Dependabot to use (more below)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;dependabot-runs-on-actions&quot;&gt;Dependabot runs on actions&lt;/h2&gt;
&lt;p&gt;Dependabot for Enterprise Server runs on actions and are visible from the ‘Actions’ tab for the repo. That way you can monitor their runs. If you enable all the features, then runs will be scheduled. Only until you actually go and check those runs, you’ll learn that it is using a ‘dependabot’ label to target the runner with. This is of course so that you can control where these runs happen, and how many runners can be bothered with these jobs. That way Dependabot runs will not just randomly start hogging all of your runners that are labeled with ‘self-hosted’. So, you do need to create runners with that label, before anything starts to happen.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Hint: checkout the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dependabot Version updates&lt;/code&gt; workflow run logs, that magically will appear in the ‘actions’ tab!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Because there is no API for Dependabot to monitor it’s runs, or a way to see queued runs and alert on them if they are queued for more then 30 minutes (for example), there is no good way to learn about this until you start searching around.&lt;/p&gt;

&lt;p&gt;Finding out about this happened because I went looking for the settings and found the stuck in queue workflow run. Looking at the workflow definition told me what I needed to know. This is quite a bit hidden in the docs: if you know where to find it, and dig through 4 different pages of information on enabling Dependabot, then eventually you find &lt;a href=&quot;https://docs.github.com/en/enterprise-server@3.6/admin/github-actions/enabling-github-actions-for-github-enterprise-server/managing-self-hosted-runners-for-dependabot-updates&quot;&gt;this reference&lt;/a&gt; to it, hidden under step 3. I made a suggestion to make it more clear you need to do this &lt;a href=&quot;https://github.com/github/docs/pull/21211&quot;&gt;here&lt;/a&gt; (update: PR accepted!).&lt;/p&gt;

&lt;h2 id=&quot;dependabot-uses-a-preconfigured-workflow-definition&quot;&gt;Dependabot uses a preconfigured workflow definition&lt;/h2&gt;
&lt;p&gt;This definition pulls down the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setup-node&lt;/code&gt; from your actions organization on the server. If you overwrite them with something custom (as we had to do since our runners are not allowed to download things from the internet), these runs can easily fail (for us they did). I have not found a way to override this workflow file, so you need to make sure this works. We currently are working on setting things up for us, which is halting our adoption of Dependabot at the moment ☹️.&lt;/p&gt;

&lt;h1 id=&quot;log-storage-is-external-to-the-server&quot;&gt;Log storage is external to the server&lt;/h1&gt;
&lt;p&gt;GitHub Actions on the SaaS version (github.com) has been created with running on Azure in mind. All of those logs are stored then on cloud storage. Currently Azure Blob storage and AWS S3 Storage is supported. If you cannot push the logs to one of those, there is a &lt;a href=&quot;https://min.io/&quot;&gt;Min.io&lt;/a&gt; container setup that has the exact API surface of an S3 bucket that can be used, to layer that on top of you own storage solution. You will need to create and configure that storage to be used before you can continue.&lt;/p&gt;

&lt;h1 id=&quot;github-connect-is-mandatory&quot;&gt;GitHub Connect is mandatory&lt;/h1&gt;
&lt;p&gt;To be able to use GitHub Actions, you need to have GitHub Connect enabled. This is a feature that allows you to connect your GitHub Enterprise Server instance to GitHub.com. This is needed to be able to use the GitHub Marketplace. I’m not really sure why this is the case, because the runners will use their own internet connection to download the actions from github.com. The only reason I see would be the UI in the workflow editor, that allows you to search the public marketplace for actions.&lt;/p&gt;

&lt;h1 id=&quot;think-about-github-actions-and-using-them-securely&quot;&gt;Think about GitHub Actions and using them securely&lt;/h1&gt;
&lt;p&gt;I written about how to use GitHub Actions with a secure setup &lt;a href=&quot;/blog/2021/2021/12/11/GitHub-Actions-Maturity-Levels&quot;&gt;here&lt;/a&gt; and you can view a recording of my conference session at Code Europe 2022 &lt;a href=&quot;/blog/2022/05/30/Code-Europe&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You really need to check the actions and vet them, since the security model is based on trust. Read more on the state of the GitHub Actions Marketplace &lt;a href=&quot;/blog/2022/09/18/Analysing-the-GitHub-marketplace&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Techorama NL: Protect yourself against supply chain attacks</title>
			<link href="https://devopsjournal.io/blog/2022/10/05/Techorama-NL"/>
			<updated>2022-10-05T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2022/10/05/Techorama-NL</id>
			<content type="html">&lt;p&gt;Placeholder for sharing the slide deck for Techorama: &lt;a href=&quot;https://techorama.nl/speakers/session/protect-yourself-against-supply-chain-attacks&quot;&gt;session link&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;abstract&quot;&gt;Abstract:&lt;/h2&gt;
&lt;p&gt;Attacks against your pipelines are more and more common these days. We’ll go over the attack vectors you need to be aware of and how someone could potentially misuse a simple setting to hijack your environment, with very large consequences. From breaking out of your shell scripts in the CI/CD pipeline, misusing typo’s in third party packages or even squatting your internal package names on a public repository: there are lots of ways to get into your pipeline!&lt;/p&gt;

&lt;h2 id=&quot;slides&quot;&gt;Slides:&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;/slides/20221012%20Techorama%20NL%20-%20Protect%20yourself%20against%20supply%20chain%20attacks%20through%20your%20pipeline.pdf&quot;&gt;&lt;img src=&quot;/images/2022/20221012/20221005_TechoramaNL.png&quot; alt=&quot;Techorama opening slide for the session&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Presentation dotnetsheff - Protect your code with GitHub security features</title>
			<link href="https://devopsjournal.io/blog/2022/09/21/dotnetsheff-github-security-features"/>
			<updated>2022-09-21T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2022/09/21/dotnetsheff-github-security-features</id>
			<content type="html">&lt;p&gt;I have the pleasure of virtually speaking at &lt;a href=&quot;https://dotnetsheff.co.uk/&quot;&gt;dotnetsheff&lt;/a&gt; and these are the slides for it:&lt;/p&gt;

&lt;p&gt;Creating modern software has a lot of moving parts. We all build on top of the shoulders of giants by leveraging closed/open source packages or containers that other people have shared. That makes securing our software a lot more complex as well!&lt;/p&gt;

&lt;p&gt;In this session you’ll learn what possible attack vectors you need to look for, how to protect yourself against them and how to leverage GitHub’s features to make your life easier!&lt;/p&gt;

&lt;p&gt;Topics:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Signed Commits&lt;/li&gt;
  &lt;li&gt;Dependabot updates&lt;/li&gt;
  &lt;li&gt;Dependency scanning for known vulnerabilities&lt;/li&gt;
  &lt;li&gt;Secret scanning (and revoking) out of the box&lt;/li&gt;
  &lt;li&gt;Using CodeQL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can download the slides &lt;a href=&quot;https://devopsjournal.io/slides/20220921%20dotnetsheff%20-%20Protect%20your%20code%20with%20GitHub%20security%20features.pdf&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://devopsjournal.io/slides/20220921%20dotnetsheff%20-%20Protect%20your%20code%20with%20GitHub%20security%20features.pdf&quot;&gt;&lt;img src=&quot;/images/2022/20220921/20220921_OpeningSlide.png&quot; alt=&quot;Opening slide of the presentation&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;questions&quot;&gt;Questions:&lt;/h2&gt;
&lt;p&gt;Some questions that came up during the presentation:
Q: Can we configure Dependabot to use conventional commits?
A: No you can’t. There are several issues on the repo that ask for this, but they have not included it. What you can do, is work with prefixes in the commit message. Read more on this from the &lt;a href=&quot;https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#commit-message&quot;&gt;docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Q: Can you run Dependabot locally?
A: You can through a local install or from a Docker container, for example using this &lt;a href=&quot;https://github.com/dependabot/dependabot-script&quot;&gt;project&lt;/a&gt;. But for a feature rich experience, you should use the GitHub setup.&lt;/p&gt;

&lt;hr /&gt;
&lt;h2 id=&quot;github-advanced-security&quot;&gt;GitHub Advanced Security&lt;/h2&gt;
&lt;p&gt;Want to learn more about these GitHub Advanced Security features?&lt;br /&gt;
Check out the LinkedIn Learning course I made for it:&lt;br /&gt;
&lt;a href=&quot;https://www.linkedin.com/learning/github-advanced-security&quot;&gt;&lt;img src=&quot;/images/LinkedIn_Learning/GitHub_Advanced_Security_02_900x505.png&quot; alt=&quot;Image of my GitHub Advanced Security course at LinkedIn Learning&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h5 id=&quot;click-the-image-to-go-to-linkedin-learning&quot;&gt;Click the image to go to LinkedIn Learning&lt;/h5&gt;
</content>
		</entry>
	
		<entry>
			<title>Analyzing the GitHub marketplace - Dependency security is a big issue</title>
			<link href="https://devopsjournal.io/blog/2022/09/18/Analysing-the-GitHub-marketplace"/>
			<updated>2022-09-18T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2022/09/18/Analysing-the-GitHub-marketplace</id>
			<content type="html">&lt;p&gt;I have been a fan of GitHub Actions since the beta in the end of 2019. And the more I use them and create my own, the more I have this growing itch to see how these actions are made, how active the community is, and what we can do to improve this ecosystem. So I decided to do some research and see what I could find out. I already have a (now inactive) &lt;a href=&quot;https://twitter.com/githubactions&quot;&gt;Twitter bot&lt;/a&gt; that scrapes the &lt;a href=&quot;https://github.com/marketplace?category=&amp;amp;query=&amp;amp;type=actions&amp;amp;verification=&quot;&gt;GitHub Actions Marketplace&lt;/a&gt; and stores that info for later use (unfortunately, the marketplace has no API to use for this).
&lt;img src=&quot;/images/2022/20220918/ashes-sitoula-UfEyDdXlRp8-unsplash.jpg&quot; alt=&quot;Photo of a lightbulb on the grass&quot; /&gt;&lt;/p&gt;
&lt;h5 id=&quot;photo-by-ashes-sitoula-on-unsplash&quot;&gt;Photo by &lt;a href=&quot;https://unsplash.com/@awesome?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Ashes Sitoula&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/s/photos/bulb?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/h5&gt;

&lt;p&gt;I am also fascinated with the security aspects of using GitHub Actions for my workloads. My first conference session on this topic was at &lt;a href=&quot;https://devopsjournal.io/blog/2021/2021/01/28/GitHub-Actions-NDC-London&quot;&gt;NDC London in January, 2021&lt;/a&gt; and I have been advocating on these learnings ever since. That is why I also decided to run my usual security checks on the entire marketplace, starting with forking them so I can enable Dependabot on the forked repositories.&lt;/p&gt;

&lt;p&gt;The Marketplace shows us almost &lt;strong&gt;15 thousand&lt;/strong&gt; actions that are available for us to use 😱. That means there is lots of community engagement for creating these actions for us, but also lots of potential for malicious actors to create actions that can be used to compromise our systems. Do be aware that in this post I’ll only be taking actions into account that have been published to the marketplace. Since any public repo with an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;action.yml&lt;/code&gt; in the root directory (and a bit more options) can be used inside of a workflow, there are many more actions that are available to us that are not part of this research.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2022/20220918/20220918_GitHubMarketplace.png&quot; alt=&quot;Screenshot of the GitHub Actions Marketplace showing 14955 actions available&quot; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Note: I’ve updated the analysis to disregard the DevDependencies after that information was added to the API. This means that the numbers are lower than the ones in the original post: a large portion of the alerts where coming from DevDependencies.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;analysis-of-the-actions-from-the-github-actions-marketplace&quot;&gt;Analysis of the actions from the GitHub Actions Marketplace&lt;/h2&gt;
&lt;p&gt;I created a new &lt;a href=&quot;https://github.com/rajbos/actions-marketplace-checks&quot;&gt;repo&lt;/a&gt; to run these checks using GitHub Actions by scheduling a workflow that runs every hour and checks the dataset for new actions that have not been forked to my validation organization yet. If you have more checks or type of information you would like to see, definitely &lt;a href=&quot;https://github.com/rajbos/actions-marketplace-checks/issues&quot;&gt;let me know&lt;/a&gt; and I’ll add them to the workflow.&lt;/p&gt;

&lt;p&gt;Some caveats up front:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;I could only load the information for 10.5 thousand actions. All the others have issues that makes it that I cannot find them anywhere. These are not included in the dataset for this analysis.&lt;/li&gt;
  &lt;li&gt;Some have been archived by their maintainer, but still show up in the Marketplace. These are of course older and have more security issues in them. The actions are included in this analysis. I’m planning to remove these when the Marketplace doesn’t show them anymore.&lt;/li&gt;
  &lt;li&gt;There are some actions where I could not parse the definition file (if used), often because of duplicate keys in their definition file. I’ve reached out to some of the maintainers to get those fixed, but also want to improve my method of loading these kinds of files. Currently the library I use for this does not support duplicate keys and throws unrecoverable errors when it finds them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’ve reported this information back to GitHub and they are planning to improve the freshness of the data in the Marketplace. Still, this is a good two thirds of the actions that are available in the marketplace, so this is a representable dataset to look at.
Examples of actions that show up in the marketplace but will 404 when you want to load the detail information for them:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/marketplace?type=actions&amp;amp;query=c-documentation-generator+&quot;&gt;c-documentation-generator&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/marketplace?type=actions&amp;amp;query=cross-commit+&quot;&gt;cross-commit+&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additionally all this analysis is done on the default branch for the repository. I have one action for example that uses a Dockerfile in the main branch, but I am working on converting it to Node in another branch. This number should be small enough to have no significant impact on the overall analysis.&lt;/p&gt;

&lt;h2 id=&quot;overall-stats---action-type&quot;&gt;Overall stats - Action type&lt;/h2&gt;
&lt;p&gt;My first interest was analyzing the overall makeup of the actions. For example, you can define the actions in multiple ways:
By the ecosystem they use: Node based (Typescript or JavaScript), Docker based or it can be a &lt;a href=&quot;https://docs.github.com/en/actions/creating-actions/creating-a-composite-action&quot;&gt;composite action&lt;/a&gt;.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Type of Action&lt;/th&gt;
      &lt;th&gt;Count&lt;/th&gt;
      &lt;th&gt;Percentage&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Node based&lt;/td&gt;
      &lt;td&gt;4,7k&lt;/td&gt;
      &lt;td&gt;45%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Docker based&lt;/td&gt;
      &lt;td&gt;3.7k&lt;/td&gt;
      &lt;td&gt;35%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Composite&lt;/td&gt;
      &lt;td&gt;1.6k&lt;/td&gt;
      &lt;td&gt;16%&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;This tells us that there is &lt;strong&gt;a lot&lt;/strong&gt; of use for Docker based actions! That means that a lot of these actions do something more complex than you can do in a Node based action. They made a choice to have a slower action, since the Docker image needs to be build or downloaded, and then needs to be booted up. Compare that with a Node base action, that immediately starts to run. But, you can use whatever language and ecosystem that fits your action the closest.&lt;/p&gt;

&lt;h3 id=&quot;action-definition-setup&quot;&gt;Action definition setup&lt;/h3&gt;
&lt;p&gt;Next up was analyzing the setup of the action itself. You can define it in multiple ways in the root of your repository:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;action.yml&lt;/code&gt; - This is the default way to define an action. It is a YAML file that contains all the information about the action.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;action.yaml&lt;/code&gt; - This is an alternate way to define an action that was available at the beginning of GitHub Actions and is therefor still supported.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dockerfile&lt;/code&gt; - This is another way to define a Docker based action. If you don’t provide an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;action.yml&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;action.yaml&lt;/code&gt; file, this one will be picked up. This Dockerfile is used by the runner and will be build on the execution environment when the action is used. An alternative way is to define the Dockerfile link in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;action.yml&lt;/code&gt; (or yaml) file. I’ve not made a specific overview that indicates how many Docker based actions where defined in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;action.yml&lt;/code&gt; file and how many have a separate Dockerfile.&lt;/li&gt;
&lt;/ul&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Definition of the Action&lt;/th&gt;
      &lt;th&gt;Count&lt;/th&gt;
      &lt;th&gt;Percentage&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;action.yml&lt;/td&gt;
      &lt;td&gt;9.2k&lt;/td&gt;
      &lt;td&gt;89%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;action.yaml&lt;/td&gt;
      &lt;td&gt;700&lt;/td&gt;
      &lt;td&gt;7%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Dockerfile&lt;/td&gt;
      &lt;td&gt;144&lt;/td&gt;
      &lt;td&gt;1%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Unknown&lt;/td&gt;
      &lt;td&gt;300&lt;/td&gt;
      &lt;td&gt;3%&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;This tells us that almost 90% of the actions have been defined with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;action.yml&lt;/code&gt; file, which is indeed the way that is show in most docs and demos.&lt;/p&gt;

&lt;h3 id=&quot;docker-based-actions&quot;&gt;Docker based actions&lt;/h3&gt;
&lt;p&gt;For the Docker based actions I was interested to see how many actions use a pre-build image from a container registry and how many do not. If they use a local Dockerfile, it will have a potentially big impact on the startup time of the action. Even worse, those are harder to support on GitHub Enterprise Server, since those environments are usually locked down from the internet, for valid security reasons. Supporting one of these actions means also supporting the ecosystems in use, starting with the base container image (run security scans on them folks!) and then anything it uses: apt-get, yum, yarn/npm, pip, etc. This will bring in some significant overhead for the support team. I’d rather recommend a maintainer to use a remote image, preferably the GitHub Container Registry, since that is directly associated with the repo and can then be downloaded without any (significant) rate limiting.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Docker based action&lt;/th&gt;
      &lt;th&gt;Count&lt;/th&gt;
      &lt;th&gt;Percentage&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Remote image&lt;/td&gt;
      &lt;td&gt;550&lt;/td&gt;
      &lt;td&gt;15%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Local Dockerfile&lt;/td&gt;
      &lt;td&gt;3.1k&lt;/td&gt;
      &lt;td&gt;85%&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;From that we learn that on-boarding and validating these Docker based actions will likely be a lot of work and will have significant impact on your runner setup: each time it is used, the base image will be downloaded and then the action will be build on top of that. This is a lot of overhead for the runner and will slow down the execution of the workflow. It also costs unnecessary bandwidth on the runner as well as extra compute power, which has an impact on the environment. All this is rather unnecessary if the maintainer would use a remote image, where the runner host can cache that image locally. Be extra aware of this if you are hosting ephemeral runners, that get deleted after they have executed a workflow job.&lt;/p&gt;

&lt;p&gt;One of the next steps here is to &lt;a href=&quot;https://github.com/rajbos/actions-marketplace-checks/issues/6&quot;&gt;analyze the remotely hosted images&lt;/a&gt; on &lt;em&gt;where&lt;/em&gt; these are hosted.&lt;/p&gt;

&lt;h2 id=&quot;repo-age&quot;&gt;Repo age&lt;/h2&gt;
&lt;p&gt;The next thing I wanted to know was the repository age: what can we tell about the last time the repo had seen (any!) updates? If the action is not maintained at all, there is a good chance there might be something wrong with it (either in functionality, security or adding in new features).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2022/20220918/20220929_01_RepoAge.png&quot; alt=&quot;Screenshot of the repo age. Highlights: oldest repo: 1121 days old. Average age: 221.6 days. Archived: 267 repos!&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I need to dive deeper to see if there are any patterns in the repo age:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Do the 267!!! archived repos bring down the average age a lot?&lt;/li&gt;
  &lt;li&gt;Do the most vulnerable repos match with the oldest repos?&lt;/li&gt;
  &lt;li&gt;What about the last release that was made? All demos show that you need to include the version in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uses&lt;/code&gt; statements, so the last time the action has seen a release might be significant as well.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;security-alerts-for-dependencies-of-the-actions&quot;&gt;Security alerts for dependencies of the Actions&lt;/h2&gt;
&lt;p&gt;I’ve also forked over the action repos to my own organization and enabled Dependabot on them to get a sense of the vulnerable dependencies they have in use. Some caveats to this analysis are:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Not every dependency will end up in the action itself, so a high alert from Dependabot will point to a ‘possibly’ vulnerable action. Since this is not something you can track automatically and see if this would be the case, we cannot be sure that the action itself is vulnerable.&lt;/li&gt;
  &lt;li&gt;This only works for the Node based actions, which is 4.7k, so almost 50% of the analyzed actions. Dependabot does not support Docker at the moment.&lt;/li&gt;
  &lt;li&gt;I’m only loading the vulnerable alerts back from Dependabot that have a severity of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;High&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Critical&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’m planning to add something like a Trivy container scan to the setup so that we get some insights from this as well.&lt;/p&gt;

&lt;h3 id=&quot;security-results&quot;&gt;Security results&lt;/h3&gt;
&lt;p&gt;Of the 10488 scanned actions, 3130 of them have a high or critical alert 😱. And that is with only scanning the Node based and Composite actions, since the Docker based actions are not scanned yet. This is a way higher than I even expected and very scary! If your dependencies already are not up to date and thus have security issues in them, how can we expect your action to be secure? That calculates to 30s% of the actions that have one or more high or critical alert on their Dependencies.&lt;/p&gt;

&lt;p&gt;To be complete: I have not filtered down the alerts to a specific ecosystem. Since GitHub Actions is one of the ecosystems Dependabot alerts on, there is a change these alerts come from a dependency on a vulnerable action for example, which would be unfair (since these will not end up in the action I am checking). Since there are only 3 actions in the &lt;a href=&quot;https://github.com/advisories?query=type%3Areviewed+ecosystem%3Aactions&quot;&gt;GitHub Advisories Database&lt;/a&gt;, I expect this to be of zero significance, but still: good to mention.&lt;/p&gt;

&lt;h3 id=&quot;diving-into-the-security-results&quot;&gt;Diving into the security results&lt;/h3&gt;
&lt;p&gt;I’ve also logged the repos with more 10 (high + critical) alerts to a separate report file and that file contains more than 600 actions!&lt;/p&gt;

&lt;p&gt;The highest number of high alerts in one singe action, is 58. Since that repo happens to be Archived, it should not be in the actions marketplace at all, as well as the fact that this should not be used at all. Luckily it is only used by a small number of workflows. I’d rather see that the runner would at least add warnings to the logs for calling actions that are archived.&lt;/p&gt;

&lt;p&gt;The highest number of critical alerts in one singe action, is 16. This repo is also only used by less then 10 other repos, so it is not a big impact. Since there is no API for finding the dependents that Dependabot finds, I cannot easily find out how many workflows are impacted by this.&lt;/p&gt;

&lt;p&gt;I’ve checked some of the repos with a lot of alerts and found one example, that has 14 high severity alerts and 2 critical alerts. This action is used by 34 different public repos (so private could even be more!). One of these dependents is a repo with 425 stars and another has 6015 stars. That last one is producing a serverless CMS that will be delivered as 48 different packages into the NPM ecosystem. One of those packages sees more than a 1000 downloads a week! This is a lot of impact for a single action that could be prevented by enabling Dependabot. Of course, more analysis is needed for this case to see if the alerts are actually relevant for the action. This depends on what the action does and how it uses the dependencies.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2022/20220918/this-is-fine.jpg&quot; alt=&quot;Image of the &apos;this is fine&apos; meme&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;overview&quot;&gt;Overview&lt;/h3&gt;
&lt;p&gt;In short, this is a top level overview of the security results:
&lt;img src=&quot;/images/2022/20220918/20220918_AllActions.png&quot; alt=&quot;Screenshot of the workflow summary, with 30% potentially vulnerable actions of the scanned actions (which is the total of scanned actions, not filtered to a specific type)&quot; /&gt;&lt;/p&gt;

&lt;p&gt;So for &lt;strong&gt;all&lt;/strong&gt; action repos I could scan, 30% have at least 1 vulnerability alert with a severity of high or critical.&lt;/p&gt;

&lt;h3 id=&quot;node-based-actions&quot;&gt;Node based actions&lt;/h3&gt;
&lt;p&gt;Filtering this down to only the Node action types, this becomes a lot scarier:
&lt;img src=&quot;/images/2022/20220918/20220918_NodeActions.png&quot; alt=&quot;Screenshot of the actions filtered to the Node only actions: 2752 actions potentially vulnerable, 1986 actions not vulnerable&quot; /&gt;&lt;br /&gt;
That is 58% of the Node actions that have at least 1 vulnerability alert with a severity of high or critical! And all demos and docs still indicate you can just use the actions as is and only hint at the security implications of that!&lt;/p&gt;

&lt;p&gt;Want to learn how to improve your security stance fur using actions? Check out this guide I made: &lt;a href=&quot;https://devopsjournal.io/blog/2021/2021/12/11/GitHub-Actions-Maturity-Levels&quot;&gt;GitHub Actions Maturity Levels&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;There is &lt;strong&gt;a lot&lt;/strong&gt; of improvement for the actions ecosystem to be made. I would like to see GitHub take a more active role in this, by for example:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Enforce certain best practices before you can publish an action to the marketplace&lt;/li&gt;
  &lt;li&gt;Cleanup the marketplace when an action’s repo gets archived (work for this is underway)&lt;/li&gt;
  &lt;li&gt;Add a security score to the marketplace, so that users can see how secure an action is, run at least these type of scans on the action repo and report it back to the end user&lt;/li&gt;
  &lt;li&gt;Add a check that validates you also pushed a new release of the action to prevent maintainers to add Dependabot and keep their (vulnerable) dependencies up to date, but not actually release a new version of the action.&lt;/li&gt;
  &lt;li&gt;Add API’s to not only the marketplace, but also Dependabot
Of course, as maintainers of actions we are also in this together! It’s our responsibility to make sure our actions are secure and that we keep them up to date. I hope this post will help you understand why to do that!&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;appendix&quot;&gt;Appendix&lt;/h2&gt;
&lt;p&gt;Maybe I can write a scanner action that looks at this information somehow. and inject that into the &lt;a href=&quot;https://github.com/marketplace/actions/load-used-actions&quot;&gt;load used actions&lt;/a&gt; action that I created.&lt;/p&gt;

&lt;p&gt;I’ll need to add this type of information for example to the &lt;a href=&quot;https://github.com/rajbos/actions-marketplace&quot;&gt;Private Actions Marketplace&lt;/a&gt; setup for example.&lt;/p&gt;

&lt;p&gt;Do you have ideas on what to add to the scans or how we can improve the Actions ecosystem? Please drop a comment below!&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>My GitHub Actions workflows are not starting</title>
			<link href="https://devopsjournal.io/blog/2022/08/12/workflows-not-starting"/>
			<updated>2022-08-12T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2022/08/12/workflows-not-starting</id>
			<content type="html">&lt;h2 id=&quot;check-the-github-status&quot;&gt;Check the GitHub status!&lt;/h2&gt;
&lt;p&gt;Every once in a while there is an outage on e.g. GitHub Actions, and I see a lot of influx of users on this blogpost. So before you start reading this, check the &lt;a href=&quot;https://www.githubstatus.com/&quot;&gt;GitHub status page&lt;/a&gt; to see if there is an outage. If there is, you can wait until it is resolved. If there is not, you can continue reading this post to see if you can find the cause of your issue. Outages can always happen in any SaaS (Software as a Service) environment and keeping a large scale environment in a healthy state is not an easy task. So if you are experiencing an outage, be empathic with the engineers who are solving the issue 🤗.&lt;/p&gt;

&lt;h2 id=&quot;caveats-some-triggers-only-work-on-the-default-branch&quot;&gt;Caveats, some triggers only work on the default branch&lt;/h2&gt;
&lt;p&gt;Some default caveats that new GitHub Actions users run into is that their workflows are not being triggered or that the UI to do so is missing. In the beginning everyone starts with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;on: push&lt;/code&gt; trigger but there will come a time that you only want to execute some workflows on the default (main) branch. So you limit the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;on: push&lt;/code&gt; trigger to that branch:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;branches&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;main&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When you follow &lt;a href=&quot;/blog/2019/07/10/DevOps-Principles-series&quot;&gt;best practices&lt;/a&gt; you want to implement a Pull Request based process to prevent a single person from making changes to a repository. That means that you are making your changes to workflows in a feature branch and not in your main branch.&lt;/p&gt;

&lt;p&gt;What happens next? See in the steps below. You can start at the top (scheduled run not starting) and then work your way down to the more specific examples. This also represents the order in which a lot of times these questions will occur.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2022/20220812/sora-sagano-MKE7NKsaBZM-unsplash.jpg&quot; alt=&quot;Photo of a street crossing, taken from above so it is upside down&quot; /&gt;&lt;/p&gt;
&lt;h5 id=&quot;photo-by-sora-sagano-on-unsplash&quot;&gt;Photo by &lt;a href=&quot;https://unsplash.com/@sorasagano?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Sora Sagano&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/s/photos/up-side-down?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/h5&gt;

&lt;h2 id=&quot;first-things-first-file-location&quot;&gt;First things first: file location&lt;/h2&gt;
&lt;p&gt;I have ran into this myself and looked for 15 minutes before I saw what was wrong: check the location of your workflow file! When you are creating the first workflow in a repo, you might learn you have configured the wrong directory because the UI does not show the workflow file if it is in the wrong folder. That can be a hint 😉.&lt;/p&gt;

&lt;p&gt;It needs to be stored in the following folder: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/.github/workflows/&lt;/code&gt;. And that folder is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;workflows&lt;/code&gt; (plural) and &lt;strong&gt;not&lt;/strong&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;workflow&lt;/code&gt; (singular).&lt;/p&gt;

&lt;h2 id=&quot;next-pushing-with-a-user&quot;&gt;Next, pushing with a user?&lt;/h2&gt;
&lt;p&gt;If you make changes within a workflow and did not set any user information (a Personal Access Token or with a GitHub App, more info &lt;a href=&quot;blog/2022/01/03/GitHub-Tokens&quot;&gt;in this post&lt;/a&gt;), then you are using the GitHub Actions Bot (a GitHub App under the covers), that creates a default GITHUB_TOKEN that can have write access to your repository. GitHub has built in that this token &lt;strong&gt;never triggers a workflow run&lt;/strong&gt;, to prevent an endless loop of actions triggering workflows, that write files, create issues, or something else, and then trigger another actions run.&lt;/p&gt;

&lt;h2 id=&quot;scheduled-runs-not-starting&quot;&gt;Scheduled runs not starting&lt;/h2&gt;
&lt;p&gt;When you add your first schedule for a daily run, you might be surprised that it does not start at the schedule you have set. You might scratch you head and wait for a couple of days, and nothing will happen.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;schedule&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;cron&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;  &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;5,17&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;*&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The cause of this is that scheduled runs &lt;em&gt;only&lt;/em&gt; trigger from the default branch (main). Several triggers behave this way, like a Pull Request (+Status) trigger, the issue / label / comment triggers, etc.&lt;/p&gt;

&lt;p&gt;So if you have a schedule in your workflow and you are not on the default branch, the workflow will not start. This is a security measure to prevent someone from creating a workflow that runs on a schedule and then creating a pull request to a repository that has a scheduled workflow. The scheduled workflow would then run on the pull request and the attacker could do something malicious. This is a good thing, but it can be confusing when you are just starting out. The solution is to make sure that you are on the default branch when you create your scheduled workflow, so that means that you need to merge in your changes for it to start based on your schedule.&lt;/p&gt;

&lt;h3 id=&quot;other-reasons-for-schedules-to-not-trigger&quot;&gt;Other reasons for schedules to not trigger&lt;/h3&gt;
&lt;p&gt;There are other reasons why a schedule might not trigger:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;The schedule event can be delayed during periods of high loads of GitHub Actions workflow runs. High load times include the start of every hour. To decrease the chance of delay, schedule your workflow to run at a different time of the hour.&lt;/li&gt;
  &lt;li&gt;The workflow might have been disabled. On forks all workflows get disabled and you need to manually enabled them (makes sense of course). Additionally: in a public repository, scheduled workflows are automatically disabled when no repository activity has occurred in 60 days. The account that last changed the workflow will get an email notification after around 23 days of inactivity in a repository:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;/images/2022/20220812/20220812_EmailNotification.png&quot; alt=&quot;Screenshot of the mail message being send that the workflow will be disabled soon&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;manual-runs-workflow_dispatch-ui-is-not-visible&quot;&gt;Manual runs (workflow_dispatch) UI is not visible&lt;/h2&gt;
&lt;p&gt;This is the common next step when the schedule does not start: you just add a workflow dispatch trigger to the workflow to trigger it manually. But since this is a new workflow that has not existed yet, the UI for it to trigger is not visible! This is the same as creating a new workflow file with this trigger in one go.&lt;/p&gt;

&lt;p&gt;For a manual trigger, &lt;em&gt;the UI is only available from the default branch&lt;/em&gt;. You can choose which branch to trigger the run from then, and have the inputs available from the default branch. But the file and the trigger has to be on the default branch for the UI to be visible.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2022/20220812/20220812_Workflow_dispatch.png&quot; alt=&quot;Screenshot of the workflow dispatch UI with the branch selector open&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You have two options to proceed and trigger the workflow:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Go to the next step and use ‘on: push’&lt;/li&gt;
  &lt;li&gt;Trigger the new workflow, from the branch by using the API&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;triggering-the-workflow-with-the-api&quot;&gt;Triggering the workflow with the API&lt;/h3&gt;
&lt;p&gt;You can trigger a workflow dispatch (as well as a &lt;a href=&quot;https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#repository_dispatch&quot;&gt;repository dispatch&lt;/a&gt; for that matter) using the UI and even trigger it from a branch.&lt;/p&gt;

&lt;p&gt;You need to make an (authenticated) call to the url for your workflow:&lt;br /&gt;
‘https://api.github.com/repos/{OWNER}/{REPO}/actions/workflows/{WORKFLOW_ID}/dispatches’&lt;br /&gt;
The workflows actually have an ID under the covers, but you can also use the filename, which is easier to read. So when the workflow is named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;get-action-data.yml&lt;/code&gt; and it lives in the repo &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rajbos/actions-marketplace&lt;/code&gt; it becomes this url:
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://api.github.com/repos/rajbos/actions-marketplace/actions/workflows/get-action-data.yml/dispatches&lt;/code&gt;. Include a JSON payload with the branch you are referencing in a “ref” property (see Postman screenshot below).&lt;/p&gt;
&lt;h5 id=&quot;note-do-not-include-a-slash-at-the-end-of-the-url-githubs-apis-do-not-accept-that-and-will-return-errors&quot;&gt;Note: do not include a slash at the end of the url, GitHub’s API’s do not accept that and will return errors.&lt;/h5&gt;

&lt;p&gt;My tool of choice for this is &lt;a href=&quot;https://www.postman.com/product/rest-client/&quot;&gt;Postman&lt;/a&gt;, because I can store my requests in it and it lives in its own window. This makes it super easy to navigate to and hit CTRL+ENTER to trigger the call, which is helpful when you are creating the workflows.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2022/20220812/20220812_Postman.png&quot; alt=&quot;Screenshot of Postman with a push to the dispatch api&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You can also use the &lt;a href=&quot;https://cli.github.com/manual/gh_workflow_run&quot;&gt;GitHub CLI&lt;/a&gt; to trigger the workflow, by running the following command from the repository folder:&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  gh workflow run get-action-data.yml &lt;span class=&quot;nt&quot;&gt;--ref&lt;/span&gt; rajbos-patch-1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Do not forget to include the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ref&lt;/code&gt; here if you want to run from a branch. Otherwise it will run from the default branch. Also be aware that this will execute in the context of the repo content that is ‘&lt;strong&gt;on GitHub&lt;/strong&gt;, so not your local content!&lt;/p&gt;

&lt;h2 id=&quot;on-push-then&quot;&gt;On: push then?&lt;/h2&gt;
&lt;p&gt;The last option you have is to just trigger the workflow whenever someone pushed data into the repository. You can decide if that should happen on certain branches but the best tip here is to include a path filter (see the docs &lt;a href=&quot;https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#push&quot;&gt;here&lt;/a&gt;).&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;branches&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;main&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;my-test-branch&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;src/**&apos;&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;.github/workflows/my-workflow-file.yml&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I often run the workflow on at least my test branch, but then &lt;strong&gt;only&lt;/strong&gt; when the relevant files for that workflow have been edited. That usually is the workflow file itself and maybe certain source files in the repo that are used: whenever there is a change in those files: execute the workflow. This is especially helpful during the development of the workflow: if you push a change in it, it is a good change that you want to trigger the workflow 😄.&lt;/p&gt;

&lt;h2 id=&quot;filtering-the-trigger-with-a-globbing-pattern&quot;&gt;Filtering the trigger with a globbing pattern?&lt;/h2&gt;
&lt;p&gt;When you are using for example the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;paths&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tags&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;branches&lt;/code&gt; filter, you can use a globbing pattern to match the files you want to trigger the workflow on. Through a quirk in the yaml spec, when your pattern starts or ends with a star, you have to encapsulate the pattern in quotes.&lt;/p&gt;

&lt;p&gt;So this setup does not work:&lt;/p&gt;
&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;branches&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;main&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;my-test-branch&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And has to be written like this:&lt;/p&gt;
&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;branches&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;main&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;my-test-branch&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;*&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;big-changes-might-prevent-the-workflow-from-being-triggered-as-well&quot;&gt;Big changes might prevent the workflow from being triggered as well&lt;/h2&gt;
&lt;p&gt;Workflows do not start when you push a large amount of files with changes to GitHub. The documentation mentions this on &lt;a href=&quot;https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#git-diff-comparisons&quot;&gt;the diff section&lt;/a&gt;: Diffs are limited to 300 files. If there are files changed that aren’t matched in the first 300 files returned by the filter, the workflow will not run. You may need to create more specific filters so that the workflow will run automatically.&lt;/p&gt;

&lt;p&gt;So if you have a filter setup on the paths of a file, and the first 300 files do not match anything in the filter, the workflow will not trigger. Recommendation is to always make your changeset as small as possible!.&lt;/p&gt;

&lt;h2 id=&quot;targeting-a-wrong-label&quot;&gt;Targeting a wrong label&lt;/h2&gt;
&lt;p&gt;If you target a runner that you do not have access to (check the runner group permissions) or a label for which no runner exists, your job will hang around until the timeout of 24 hours has passed. Then it will get a timeout error and the job will be cancelled. Double check the label you are using (this typo in the screenshot always gets me!) and fix it, or check the permissions on the runner group.&lt;br /&gt;
&lt;img src=&quot;/images/2022/20220812/20220812_RunnerLabel.png&quot; alt=&quot;Screenshot of the logs that indicate we are targeting the label &apos;ubununtu-latest&apos;, which does not exists&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;final-remark&quot;&gt;Final remark&lt;/h1&gt;
&lt;p&gt;Note that this all will not help when you have specific use cases you want to test, like when someone creates a comment, a pull request. There are other ways to deal with that, but that is for another post.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Creating a GitHub Action</title>
			<link href="https://devopsjournal.io/blog/2022/06/01/creating-a-github-action"/>
			<updated>2022-06-01T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2022/06/01/creating-a-github-action</id>
			<content type="html">&lt;p&gt;I wanted to describe how my flow usually works for creating a &lt;a href=&quot;https://docs.github.com/en/actions&quot;&gt;GitHub Actions&lt;/a&gt;. People often struggle to think of something to build because they start with an empty canvas: the action itself. That is not how I build up the action. For me the process is as follows:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Have a need for something straightforward: like calling the GitHub API in a certain way.&lt;/li&gt;
  &lt;li&gt;Create a small &lt;a href=&quot;https://github.com/actions/github-script&quot;&gt;github-script&lt;/a&gt; for it to see if this works.&lt;/li&gt;
  &lt;li&gt;Move from the inline script to an actual script file, since that is easier to debug.&lt;/li&gt;
  &lt;li&gt;Have a fully working script, and realize this could be helpful for others&lt;/li&gt;
  &lt;li&gt;Create a new action repo for it and move the code there&lt;/li&gt;
  &lt;li&gt;Add unit tests to validate everything works&lt;/li&gt;
  &lt;li&gt;Add a local workflow that tests the action&lt;/li&gt;
  &lt;li&gt;Use the action repo in the place where it started, validate it works there as well&lt;/li&gt;
  &lt;li&gt;Publish the action&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;/images/2022/20220601/mika-baumeister-Y_LgXwQEx2c-unsplash.jpg&quot; alt=&quot;Photo of a sign that says: Turn ideas into reality&quot; /&gt;
&lt;span class=&quot;rTNyH RZQOk&quot;&gt;Photo by &lt;a href=&quot;https://unsplash.com/@mbaumi?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Mika Baumeister&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;.
  &lt;/span&gt;&lt;/p&gt;

&lt;h2 id=&quot;have-a-need-for-something-straightforward&quot;&gt;Have a need for something straightforward&lt;/h2&gt;
&lt;p&gt;I work a lot with GitHub Enterprise server these days, and we have self hosted runners. I wanted to load the number of available runners by trait and alert me when the number of a certain trait is beneath 3. You can call the API at the organization level to retrieve that information: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET /repos/{owner}/{repo}/actions/runners&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;create-a-small-github-script-for-it-to-see-if-this-works&quot;&gt;Create a small &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github-script&lt;/code&gt; for it to see if this works.&lt;/h2&gt;
&lt;p&gt;With the &lt;a href=&quot;https://github.com/actions/github-script&quot;&gt;github-script&lt;/a&gt; you get an authenticated GitHub client to use that can make calls to the API. Since this is an organization level API, we need to give it an access token for it to use. Since I like to use a GitHub App for that, I use that to get a token an pass it in.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;c1&quot;&gt;# example of loading a token from an GitHub App&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Get Token&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;get_workflow_token&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;peter-murray/workflow-application-token-action@v1&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;application_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;$&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;application_private_key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;$&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then I call the API with the client:&lt;/p&gt;
&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;octokit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;GET /repos/{owner}/{repo}/actions/runners&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;owner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;organization&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;repo&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`Found &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;total_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; runners at the repo level`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The data object will have the response of the call and I can start working on manipulating it to get the representation I want, like grouping them per label:&lt;/p&gt;
&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;group&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;runners&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;runner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;runner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;findIndex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;runner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;online&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;// existing group                &lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;counter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;counter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;status&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;// new group&lt;/span&gt;
                &lt;span class=&quot;nx&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;counter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;I can now search for a certain group and alert if the count for that group is less then the number I want.&lt;/p&gt;

&lt;h2 id=&quot;move-from-the-inline-script-to-an-actual-script-file-since-that-is-easier-to-debug&quot;&gt;Move from the inline script to an actual script file, since that is easier to debug.&lt;/h2&gt;
&lt;p&gt;The hard part of using the &lt;a href=&quot;https://github.com/actions/github-script&quot;&gt;github-script&lt;/a&gt; is that it is very hard to debug. Your script is inlined somewhere so you get an error message stating there is a parsing issue with the inline script, which will be something vague like: error parsing ‘ on line 57.  You can try to count the line number in your inline script and search for it that way, but that is very brittle and just sucks. To improve on this, we can move the entire script into a separate file, which we can then call from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github-script&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;New file –&amp;gt; Move the code to a separate file. I often save the file in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.github/workflows&lt;/code&gt; folder since that it is what it is for. Don’t forget to checkout the repo since now we need to have the file on disk before we can execute it:&lt;/p&gt;
&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v3&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;github-script@v5&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
       &lt;span class=&quot;s&quot;&gt;const script = require(&apos;./path/to/script.js&apos;)&lt;/span&gt;
       &lt;span class=&quot;s&quot;&gt;console.log(script({github, context}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;If there are parsing errors, the parsing engine will actually tell you: there is an error in you script.js on line 25. Much better!&lt;/p&gt;

&lt;h2 id=&quot;have-a-fully-working-script-and-realize-this-could-be-helpful-for-others&quot;&gt;Have a fully working script, and realize this could be helpful for others&lt;/h2&gt;
&lt;p&gt;Now that we have a fully working script, with the alerts, I can start thinking about how this could be useful for others. Some people might just want to get the grouped information, some might want to generate alerts. An alert could be a failed workflow, and then use the GitHub notification system to alert the owner. Or the alert might be send into a Teams channel, or a Slack message. Since that could be anything, I want to make only the building block needed to get the info we need. It’s then up to the user to figure out what they want to do with it: they can choose to make an alert and then configure where to send it.&lt;/p&gt;

&lt;p&gt;As a good practice, you try to keep your action as minimal as possible. I have actions that only get the data I need, and return it as JSON. What happens with the JSON is up to them. I have cases where I save the JSON back into a repo, or where I load the JSON and then filter it. The point is: leave that up to the user: they can build on you building block.&lt;/p&gt;

&lt;h2 id=&quot;create-a-new-action-repo-for-it-and-move-the-code-there&quot;&gt;Create a new action repo for it and move the code there&lt;/h2&gt;
&lt;p&gt;Since I determined this might be useful for others, I start creating a new action repository. A good starting point is always the &lt;a href=&quot;https://github.com/actions/typescript-action&quot;&gt;Typescript template repository&lt;/a&gt;. Use it as a template and it will give you the skeleton setup you need. Only thing that currently doesn’t work on my Windows machine is the building of it, since the setup they use just fails on Windows. Switching that to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;esbuild&lt;/code&gt; does work, so I often convert those calls.&lt;/p&gt;

&lt;h2 id=&quot;add-unit-tests-to-validate-everything-works&quot;&gt;Add unit tests to validate everything works&lt;/h2&gt;
&lt;p&gt;Since there is some intelligence in the code, like a function that handles the grouping, I start creating unit tests for that. This way I can test the code locally, which saves me a lot of time. Use .env files to load an access token and you can even execute all the API calls and verify that things work.&lt;/p&gt;

&lt;h2 id=&quot;add-a-local-workflow-that-tests-the-action&quot;&gt;Add a local workflow that tests the action&lt;/h2&gt;
&lt;p&gt;After validating the unit tests (and integration tests if you use the .env file setup), it is time to test the action inside of an workflow. You can call the local action in a workflow, pass in the needed params and then execute it:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt; 
      &lt;span class=&quot;na&quot;&gt;access_token&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;$&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;We can store the results in a JSON file for example, and pick that up in the next step in the job. Then parse it and validate it contains the data we expect.&lt;/p&gt;

&lt;h2 id=&quot;use-the-action-repo-in-the-place-where-it-started-validate-it-works-there-as-well&quot;&gt;Use the action repo in the place where it started, validate it works there as well&lt;/h2&gt;
&lt;p&gt;As a final validation I replace the script file in the first step, with a call to this new action. If it still works as expected, it is time for the next step. If not, we can fix it before telling the world about a new action 😉.&lt;/p&gt;

&lt;h2 id=&quot;publish-the-action&quot;&gt;Publish the action&lt;/h2&gt;
&lt;p&gt;Now it is time to publish the action. We can automate almost everything: creating a release from a tag is where it starts. GitHub Actions use Git Tags for the version number. I use a workflow that is triggered whenever I push a new tag to the repo: that will create the release and add the repo into it.&lt;/p&gt;

&lt;p&gt;The only thing is: publishing the action to the marketplace can currently only be done with the UI 😲. There is a checkbox that needs to be set, and that is not available from the API. As a workaround, I create the release from the workflow file, and then push a message into my own Slack channel with a link to the release. I can then follow the link, edit the release and set that checkbox.&lt;/p&gt;

&lt;h2 id=&quot;tell-the-world-about-your-new-action&quot;&gt;Tell the world about your new action&lt;/h2&gt;
&lt;p&gt;A quick shoutout on social media is the least we can do 😄.&lt;/p&gt;

&lt;h3 id=&quot;note&quot;&gt;Note:&lt;/h3&gt;
&lt;p&gt;To see the resulting action at work, and to the source code I used for all the setup, check out this action: &lt;a href=&quot;https://github.com/devops-actions/load-runner-info&quot;&gt;load-runner-info&lt;/a&gt;.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Code Europe: Protect your code with GitHub&apos;s security features</title>
			<link href="https://devopsjournal.io/blog/2022/05/30/Code-Europe"/>
			<updated>2022-05-30T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2022/05/30/Code-Europe</id>
			<content type="html">&lt;p&gt;I have the pleasure of speaking at &lt;a href=&quot;https://www.codeeurope.pl/en/speakers/rob-bos&quot;&gt;Code Europe&lt;/a&gt; and these are the slides for it:&lt;/p&gt;

&lt;p&gt;Creating modern software has a lot of moving parts. We all build on top of the shoulders of giants by leveraging closed/open source packages or containers that other people have shared. That makes securing our software a lot more complex as well!&lt;/p&gt;

&lt;p&gt;In this session you’ll learn what possible attack vectors you need to look for, how to protect yourself against them and how to leverage GitHub’s features to make your life easier!&lt;/p&gt;

&lt;p&gt;Topics:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Signed Commits&lt;/li&gt;
  &lt;li&gt;Dependabot updates&lt;/li&gt;
  &lt;li&gt;Dependency scanning for known vulnerabilities&lt;/li&gt;
  &lt;li&gt;Secret scanning (and revoking) out of the box&lt;/li&gt;
  &lt;li&gt;Using CodeQL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can download the slides &lt;a href=&quot;https://devopsjournal.io/slides/20220530%20Code%20Europe%20Protect%20your%20code%20with%20GitHub%20security%20features.pdf&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://devopsjournal.io/slides/20220530%20Code%20Europe%20Protect%20your%20code%20with%20GitHub%20security%20features.pdf&quot;&gt;&lt;img src=&quot;/images/2022/20220530/20220530_Opening.png&quot; alt=&quot;Opening slide of the presentation&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;
&lt;h2 id=&quot;github-advanced-security&quot;&gt;GitHub Advanced Security&lt;/h2&gt;
&lt;p&gt;Want to learn more about these GitHub Advanced Security features?&lt;br /&gt;
Check out the LinkedIn Learning course I made for it:&lt;br /&gt;
&lt;a href=&quot;https://www.linkedin.com/learning/github-advanced-security&quot;&gt;&lt;img src=&quot;/images/LinkedIn_Learning/GitHub_Advanced_Security_02_900x505.png&quot; alt=&quot;Image of my GitHub Advanced Security course at LinkedIn Learning&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h5 id=&quot;click-the-image-to-go-to-linkedin-learning&quot;&gt;Click the image to go to LinkedIn Learning&lt;/h5&gt;
</content>
		</entry>
	
		<entry>
			<title>Speaking at NDC Security (Oslo)</title>
			<link href="https://devopsjournal.io/blog/2022/04/06/Speaking-at-NDC-Security"/>
			<updated>2022-04-06T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2022/04/06/Speaking-at-NDC-Security</id>
			<content type="html">&lt;p&gt;I had the pleasure of giving 3 sessions at NDC Security in Oslo on April 6th and 7th. After 2,5 years of not being at a conference in real person, this was the first time speaking in front of an audience and sitting in on sessions in real life! Super weird to do in this time of turmoil in the world, but when you sign up to speak, the conference organizers are relying on you to show up of course!&lt;/p&gt;

&lt;p&gt;For me a nice way to get back to public speaking in front of a group: I’ve done my session on “How to use GitHub Actions with security in mind” a lot last year, but everything was online due to corona! The atmosphere at a conference and in front of a real live audience is completely different from doing this from your home office. Sitting in to hour long sessions is really hard work if you do this from home, and as speaker you often do get little to no feedback during the sessions from home. Honestly, the less professionally a virtual session has been arranged, the more engagement and fun I had in them! A simple meetup for example just uses a Zoom/Teams session where people can chat and ask questions. Often they also turn on their camera’s and that really helps to keep you sane. Having a feeling of talking to the wall really does not help when the conference is virtual. That interaction and connection with the audience was often missing in “professionally managed” sessions.&lt;/p&gt;

&lt;p&gt;Getting back to public speaking in front of an audience was something I was curious to see what it would do to me: will I get overwhelmed with stress, do I freak out on stage? I have gotten a lot more confident on virtual sessions over the last years, but will that pay out on stage as well?&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2022/20220406/Speaking.png&quot; alt=&quot;Rob on stage during one of the sessions&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Before getting started with my session, the last 10-5 minutes before getting on the stage I felt stress building up. Some of it lingered on through the intro part of the session, but left me relatively quickly after that. I had some people in the room that I met earlier that day and that already helps as a focus point during the session. That really kept me on track.&lt;/p&gt;

&lt;p&gt;Having a full room with 60+ people was awesome: there where actual people, nodding along, frowning if they couldn’t follow it and even asking questions in between! Having that interaction is key to deliver a good session for me, I need to have that connection I have learned.&lt;/p&gt;

&lt;p&gt;Afterwards people actually hung around and asked questions! More engagement happened during the ‘hallway track’, where people randomly come to you, tell you something on how they experience the session and some things they picked up from it. AWESOME! I got some great feedback on the things that stuck with them and learned that I still like doing this. That means I will be sending in some of these session to CFP’s (Call For Papers) again, because I wasn’t sure before.&lt;/p&gt;

&lt;h1 id=&quot;sessions-slides-and-links&quot;&gt;Sessions, slides and links:&lt;/h1&gt;
&lt;p&gt;The sessions where also recorded for people attending at home and the recordings should be shared afterwards as well! I’ll update this post when they become available, so you can watch them if you are interested.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Session &amp;amp; Slides&lt;/th&gt;
      &lt;th&gt;Link&lt;/th&gt;
      &lt;th&gt;Recording&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;/slides/20220406%20Protect%20your%20code%20with%20GitHub%20security%20features.pdf&quot;&gt;Protect your code with GitHub Security features&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://ndc-security.com/agenda/protect-your-code-with-github-security-features-0dhc/0kibw07zdja&quot;&gt;Link&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=dZYiveyMWXg&amp;amp;list=PLXVVwOM8uv2zyhtF-aHwsyDbqsm_RGOGY&amp;amp;index=3&quot;&gt;YouTube&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;/slides/20220406%20Protect%20yourself%20against%20supply%20chain%20attacks%20through%20your%20pipeline.pdf&quot;&gt;Protect yourself against supply chain attacks through your pipeline&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://ndc-security.com/agenda/protect-yourself-against-supply-chain-attacks-through-your-pipeline-0adx/0p76cipnoha&quot;&gt;Link&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=00R1JGBQEJg&amp;amp;list=PLXVVwOM8uv2zyhtF-aHwsyDbqsm_RGOGY&amp;amp;index=2&quot;&gt;YouTube&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;/slides/20220407%20How%20to%20use%20GitHub%20Actions%20with%20Security%20in%20Mind.pdf&quot;&gt;Using GitHub Actions with Security in Mind&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://ndc-security.com/agenda/how-to-use-github-actions-with-security-in-mind/888f3d8bf8fe&quot;&gt;Link&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=bDG40Y1nPEk&amp;amp;list=PLXVVwOM8uv2zyhtF-aHwsyDbqsm_RGOGY&amp;amp;index=4&quot;&gt;YouTube&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr /&gt;
&lt;h2 id=&quot;github-advanced-security&quot;&gt;GitHub Advanced Security&lt;/h2&gt;
&lt;p&gt;Want to learn more about these GitHub Advanced Security features?&lt;br /&gt;
Check out the LinkedIn Learning course I made for it:&lt;br /&gt;
&lt;a href=&quot;https://www.linkedin.com/learning/github-advanced-security&quot;&gt;&lt;img src=&quot;/images/LinkedIn_Learning/GitHub_Advanced_Security_02_900x505.png&quot; alt=&quot;Image of my GitHub Advanced Security course at LinkedIn Learning&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h5 id=&quot;click-the-image-to-go-to-linkedin-learning&quot;&gt;Click the image to go to LinkedIn Learning&lt;/h5&gt;
</content>
		</entry>
	
		<entry>
			<title>GitHub Notification Settings</title>
			<link href="https://devopsjournal.io/blog/2022/03/12/GitHub-notification-settings"/>
			<updated>2022-03-12T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2022/03/12/GitHub-notification-settings</id>
			<content type="html">&lt;p&gt;I notice a lot of people getting lost in their GitHub notifications. Here is what you can do to get some control back! The default settings send you emails for everything. A lot of people then create an email rule to move all those emails to a specific folder, which means they will never look at those emails again! With some tweaking you can make the notifications work &lt;em&gt;for&lt;/em&gt; you.&lt;/p&gt;

&lt;p&gt;If you want to see this in action, watch the video I created:&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/eIWzKR465M0&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;intro&quot;&gt;Intro&lt;/h2&gt;
&lt;p&gt;There are a couple of things at play when using notifications:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;settings for how and where you want to receive the notifications (email, web, etc.)&lt;/li&gt;
  &lt;li&gt;reasons for getting a notification&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Telling GitHub to send you a notification can happen in a few different ways. You can subscribe to updates, either at the organization level (bad idea: way to noisy), at the repository level or for a specific issue, pull request, discussion. People can also @mention you or a team that you are in, which can then trigger a notification for you.&lt;/p&gt;
&lt;h2 id=&quot;step-1-check-you-settings-on-where-you-want-to-receive-the-notifications&quot;&gt;Step 1: check you settings on &lt;em&gt;where&lt;/em&gt; you want to receive the notifications&lt;/h2&gt;
&lt;p&gt;Go to &lt;a href=&quot;https://github.com/settings/notifications&quot;&gt;github.com/settings/notifications&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;automatic-watching&quot;&gt;Automatic watching&lt;/h3&gt;
&lt;p&gt;Under this section you find two options, both of which I would like to control for each organization I am in. Unfortunately you cannot at the moment.&lt;br /&gt;
By default these checkboxes are checked, which means that you will receive notifications for all the repositories you have write settings to. If you are an admin, or like me a trainer: you will get a lot of notifications, so it’s often a good idea to turn this off. In my case, I am one of the trainers who use an organization during these trainings and give our trainees a repository &lt;strong&gt;each&lt;/strong&gt; to work in. Since we are admins on the org, we would get write access to each repo, meaning a notification for each issue and pull request that happens in it! Given that one of the exercises is to enable Dependabot on a test repository, that is a lot of noise for me!&lt;/p&gt;

&lt;p&gt;The other setting in here is for all the teams that you are added to. If your team uses discussions in GitHub to reach out to each other, this setting is often very useful to leave on.
&lt;img src=&quot;/images/2022/20220312/20220312_01.png&quot; alt=&quot;Notification settings - Automatic watching&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;participating-and-watching&quot;&gt;Participating and watching&lt;/h3&gt;
&lt;p&gt;These settings define the medium used for notifying you: email, or through the UI. Since the emails do not work for these (you can use the notifications as a personal backlog if you use the UI and not through mail), if always configure this to only use the UI (web and mobile). For triaging on the go I usually use the mobile app and then the things I really need to work on stay in the notifications list. Every once in a while I go through them and act where needed.&lt;br /&gt;
&lt;img src=&quot;/images/2022/20220312/20220312_02.png&quot; alt=&quot;Notification settings - Participating and watching&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;dependabot-alerts&quot;&gt;Dependabot alerts&lt;/h3&gt;
&lt;p&gt;With this setting the places where the notifications from Dependabot alerts (‘you have a vulnerable dependency’) are visible. You can then still configure it to also send you an email:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;when the vulnerability alert is created&lt;/li&gt;
  &lt;li&gt;and / or once a week as a digest&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/images/2022/20220312/20220312_03.png&quot; alt=&quot;Notification settings - Dependabot alerts&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;actions&quot;&gt;Actions&lt;/h3&gt;
&lt;p&gt;With these settings you can tell GitHub how to send you notifications for your workflows. Then you can tune that down a bit to only send notifications for failed workflows. I really wish they will improve this feature so that it cooperates with me looking in the UI to see if I have already watched the workflow fail: often during the creation of the workflow, you are already working in the UI to check the logs and fix them. After a while you find 40 notifications, for all the failed runs you have already looked at. I’d rather only have one notification for each failed run that I have not yet seen.
![Notification settings - Actions](/images/2022/20220312/20220312_04.png]&lt;/p&gt;

&lt;h3 id=&quot;organization-alerts&quot;&gt;Organization alerts&lt;/h3&gt;
&lt;p&gt;Not a lot of people will have these rights, but you can toggle of the notifications for when someone creates a &lt;a href=&quot;https://docs.github.com/en/developers/overview/managing-deploy-keys&quot;&gt;deploy key&lt;/a&gt;.&lt;br /&gt;
&lt;img src=&quot;/images/2022/20220312/20220312_05.png&quot; alt=&quot;Notification settings - Organization alerts&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;email-notification-preferences&quot;&gt;Email notification preferences&lt;/h3&gt;
&lt;p&gt;With these settings you can pick for which type of event your notifications emails will be send and to which one of your verified email addresses.&lt;br /&gt;
&lt;img src=&quot;/images/2022/20220312/20220312_06.png&quot; alt=&quot;Notification settings - Email notification preferences&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;custom-routing&quot;&gt;Custom routing&lt;/h3&gt;
&lt;p&gt;With custom routing you can configure the preferred email address for each organization. For example you could have a single account to login to your personal account and your work organization. Then you might prefer to send the email notifications for your work organization to your work mailbox.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2022/20220312/20220312_07.png&quot; alt=&quot;Notification settings - Custom routing&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;step-2-tune-down-the-amount-of-notifications&quot;&gt;Step 2: Tune down the amount of notifications!&lt;/h2&gt;
&lt;p&gt;On &lt;a href=&quot;https://github.com/settings/notifications&quot;&gt;github.com/settings/notifications&lt;/a&gt; you can use the filter options to find out why you are getting them: you might be ‘following’ (called watching) the repo (and thus get a notifications for anything that happens in that repo), or have &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;subscribed&lt;/code&gt; to specific pull requests or issues.&lt;/p&gt;

&lt;p&gt;In the lower left hand side of the notifications view there is a small link ‘manage notifications’ which will give you this popup:&lt;br /&gt;
&lt;img src=&quot;/images/2022/20220312/20220312_08_Settings.png&quot; alt=&quot;Settings popup&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;watched-repositories-and-subscribed-to-prs-and-issues&quot;&gt;Watched repositories and subscribed to PR’s and issues&lt;/h3&gt;
&lt;p&gt;With ‘watched repositories’ and ‘subscribed’ you can see which repositories you are watching and which pull requests or issues you are subscribed to. Since the watched repositories give the most notifications, you want to start here:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2022/20220312/20220312_09_Watching.png&quot; alt=&quot;Watched repositories&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Go through the list of repositories and figure out if you really need to be watching for &lt;strong&gt;every&lt;/strong&gt; thing that happens in it. Often you want to be notified on participating and @mentions. To turn them off, use the ‘Ignore’ settings for that repository.
&lt;img src=&quot;/images/2022/20220312/20220312_10_Watching_options.png&quot; alt=&quot;Watching options&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The power option here is the ‘Custom’ setting:&lt;br /&gt;
&lt;img src=&quot;/images/2022/20220312/20220312_11_Watching_custom.png&quot; alt=&quot;Custom watch options&quot; /&gt;&lt;br /&gt;
Now you can really configure for what things you want to be notified. So this is somewhere between the ‘All activity’ and ‘Participating and watching’ settings in.&lt;/p&gt;

&lt;h1 id=&quot;why-did-i-get-this-notification&quot;&gt;Why did I get this notification?&lt;/h1&gt;
&lt;p&gt;You can go to the notification and click on it. That will bring you to PR / issue / discussion that was the reason the notification was sent. The UI tries to help you figure out why you are receiving it:&lt;br /&gt;
&lt;img src=&quot;/images/2022/20220312/20220312_12_NotificationReason.png&quot; alt=&quot;Notification reason screenshot&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Hope this helps making your GitHub notifications more manageable. If you have any follow up questions, leave them in the comments below!&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Configuration as Code for the GitHub platform</title>
			<link href="https://devopsjournal.io/blog/2022/03/12/GitHub-config-as-code"/>
			<updated>2022-03-12T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2022/03/12/GitHub-config-as-code</id>
			<content type="html">&lt;p&gt;I am slowly diving into ‘Configuration as code’ for the GitHub Platform: all the things you want to automate with as few steps as possible, making big impact. Some of these things also fall under ‘GitOps’ in my opinion: if you store it into a repo and on changes you make, the automation will make it happen.&lt;/p&gt;

&lt;p&gt;The plan is to have this post as a central starting point for people searching to achieve a similar setup. There are loads of people who blog on how to make this happen and what works for them, but often there is no actual implementation they can share. I want to give the you the examples and give you (a copy of) the code used as well.&lt;/p&gt;
&lt;h5 id=&quot;note-since-most-of-these-items-need-to-be-running-in-a-separate-org-i-created-them-on-my-robs-tests-org&quot;&gt;Note: Since most of these items need to be running in a separate org, I created them on my &lt;a href=&quot;https://github.com/robs-tests/&quot;&gt;robs-tests org&lt;/a&gt;.&lt;/h5&gt;

&lt;h1 id=&quot;scenario1-automate-user-onboarding-and-repository-creation&quot;&gt;Scenario:1 Automate user onboarding and repository creation&lt;/h1&gt;
&lt;p&gt;For on of our trainings we invite all trainees into and organization and create teams and repositories for them. Doing this with the UI is cumbersome and error-prone. We want to automate this process where someone can edit a yaml file, lint it and through a pull request approve it so that you always need another person to verify the incoming changes. After merging a workflow starts that make the new situation happen.&lt;/p&gt;

&lt;p&gt;The steps that I want to have are as follows:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Add new users and teams to the users.yml file&lt;/li&gt;
  &lt;li&gt;Create a PR&lt;/li&gt;
  &lt;li&gt;The workflow pull_request.yml workflow checks if:
    &lt;ul&gt;
      &lt;li&gt;The user file is valid yaml&lt;/li&gt;
      &lt;li&gt;The users is a valid GitHub handle&lt;/li&gt;
      &lt;li&gt;The user is already a member of the organization&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;After merging, the user-management.yml workflow runs and:
    &lt;ul&gt;
      &lt;li&gt;Create the team if needed&lt;/li&gt;
      &lt;li&gt;Add the user to the org&lt;/li&gt;
      &lt;li&gt;Create repository attendee-&lt;userhandle&gt;&lt;/userhandle&gt;&lt;/li&gt;
      &lt;li&gt;Add the user to the repo&lt;/li&gt;
      &lt;li&gt;Add the user to the team&lt;/li&gt;
      &lt;li&gt;Add the team to the repo&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Link to repo: &lt;a href=&quot;https://github.com/robs-tests/user-test-repo&quot;&gt;robs-tests/user-test-repo&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;step-1-define-a-yaml-structure-and-parse-it&quot;&gt;Step 1: define a yaml structure and parse it&lt;/h2&gt;
&lt;p&gt;In this example I want to start a simple structure, parse it, and then loop through the results. You can do this in any format you want. I thought of doing this in json for example, but that gives you a lot of overhead with all the extra double quotes and it is a bit harder to read. It would be easy to link a user to the wrong team for example. I knew I can parse yaml with a library, and that is what I went with: it will be compact, no extra characters around the content. Since for our trainings we usually have a max of 20 people in the group, the entire team and their users will fit on the screen without scrolling.&lt;/p&gt;

&lt;h3 id=&quot;yaml-format&quot;&gt;yaml format:&lt;/h3&gt;
&lt;p&gt;This is the format I settled on for now: there is a list of teams and in each team there is a list of users.&lt;/p&gt;
&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;teams&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;team01&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;users&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; 
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;rajbos&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Maxine-Chambers&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Reading that from parsing the yaml gives me two loops to create: for each team and then for each user, do ‘x’.&lt;/p&gt;

&lt;h2 id=&quot;step-2-define-the-way-you-want-to-build-your-workflow&quot;&gt;Step 2: define the way you want to build your workflow&lt;/h2&gt;
&lt;p&gt;For most languages there will be an library available to parse the yaml, so it becomes a choice on what you would like to use for the automation. It depends on what your team already knows and how easy you want to be able to test this. These days I skip having a hard to manage setup and go to something really simple: &lt;a href=&quot;https://github.com/actions/github-script&quot;&gt;github-script&lt;/a&gt;. This is a JavaScript Action that you can give your own script file and it will execute that for you. Inside your script you then get access to the GitHub contexts with authenticated clients and calls for all the API’s you need. You can even run it with an &lt;a href=&quot;/blog/2022/01/03/GitHub-Tokens&quot;&gt;access token&lt;/a&gt; from a GitHub App (since we only use the organization level API’s here, this will work).&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/actions/github-script&quot;&gt;github-script&lt;/a&gt; also helps with having your code in JavaScript already: if you later want to make this a building block and make an action out of it, you are ready to go!&lt;/p&gt;

&lt;h2 id=&quot;step-3-the-pr-workflow&quot;&gt;Step 3: the PR workflow&lt;/h2&gt;
&lt;p&gt;In the &lt;a href=&quot;https://github.com/robs-tests/user-test-repo/blob/main/.github/workflows/pull_request.yml&quot;&gt;PR workflow&lt;/a&gt; I want to verify that:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;the yaml file is valid yaml&lt;/li&gt;
  &lt;li&gt;the handles in the file are valid GitHub handles (we sometimes get e-mail addresses or typos in the handles)&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/setup-node@v2&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;node-version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;14&lt;/span&gt;
          
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;npm install yaml&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/github-script@v5&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Run scripts&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; 
          &lt;span class=&quot;na&quot;&gt;github-token&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ secrets.GH_PAT }}&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;  
            &lt;span class=&quot;s&quot;&gt;const yaml = require(&apos;yaml&apos;)&lt;/span&gt;
            
            &lt;span class=&quot;s&quot;&gt;const repo = &quot;${{ github.repository }}&quot;.split(&apos;/&apos;)[1]&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;const owner = &quot;${{ github.repository_owner }}&quot;&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;const userFile = &quot;users.yml&quot;&lt;/span&gt;
            
            &lt;span class=&quot;s&quot;&gt;const script = require(&apos;${{ github.workspace }}/src/check-pr.js&apos;)&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;const result = await script({github, context, owner, repo, userFile, yaml})&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;console.log(``)&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;console.log(`End of workflow step`)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In this example you see that we are:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;checking out the repository&lt;/li&gt;
  &lt;li&gt;setup node&lt;/li&gt;
  &lt;li&gt;so that we can install a node package that can parse the yaml&lt;/li&gt;
  &lt;li&gt;then we use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github-script&lt;/code&gt; action to execute the script&lt;/li&gt;
  &lt;li&gt;load the file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;check-pr.js&lt;/code&gt; that will do the work&lt;/li&gt;
  &lt;li&gt;we pass in the info needed for the script to have all the context it needs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;check-pr.js&lt;/code&gt; file you can see that we are:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;loading the contents of the yaml file in the current branch: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github.rest.repos.getContents({owner, repo, path, ref})&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;get the list of current teams: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let existingTeams = await getExistingTeams(owner)&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;parse the content of the yaml file: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;const parsed = yaml.parse(content)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And then we can loop through each element in the arrays:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  for each team:
    for each user in team:
       check if user exists
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Verifying if a user exists can be done with a call to this API: https://api.github.com/users/${userHandle}
Checking if that user then already is a member of the org can be done with a call to this API: https://api.github.com/orgs/${orgName}/members/${userHandle.login}&lt;/p&gt;

&lt;h2 id=&quot;step-4-the-user-management-workflow&quot;&gt;Step 4: the user-management workflow&lt;/h2&gt;
&lt;p&gt;When the PR is merged to main, another workflow executes: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;user-management.yml&lt;/code&gt;. This has the same setup: install the npm packages, load the script and run it.&lt;/p&gt;

&lt;p&gt;From &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;load-users.js&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// send an invite to the user on the org level:&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;addUserToOrganization&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;organization&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// create a new repository for this user:&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;repoName&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`attendee-&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;login&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;createUserRepo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;organization&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;repoName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// give the user admin acccess to the repo:&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;addUserToRepo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;organization&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;repoName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// add the user to the team for the day of the training:&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;addUserToTeam&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;organization&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;team&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// add the team to the repo (so that the rest of the team can help with PR&apos;s):&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;addTeamToRepo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;organization&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;repoName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;team&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;final-thoughts&quot;&gt;Final thoughts&lt;/h1&gt;
&lt;p&gt;With this setup, you now have a complete example how you can use GitHub Actions to automate the process of adding users to an organization and preparing things like teams and repositories for them. You can build on top of this with for example:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;a setup that has a folder structure that defines the hierarchy of teams and users&lt;/li&gt;
  &lt;li&gt;each folder could then have different code owners that defines who needs to approve the PR (give the team itself self-service on who to add)&lt;/li&gt;
  &lt;li&gt;add more properties to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;users.yml&lt;/code&gt; in case you need it&lt;/li&gt;
&lt;/ul&gt;
</content>
		</entry>
	
		<entry>
			<title>GitHub Codespaces creation with the CLI</title>
			<link href="https://devopsjournal.io/blog/2022/01/26/GitHub-Codespace-creation-with-the-cli"/>
			<updated>2022-01-26T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2022/01/26/GitHub-Codespace-creation-with-the-cli</id>
			<content type="html">&lt;p&gt;This one took me some time to figure out, so I wanted to have something for the next time I need it :-).
I needed to create repos for a team and then create a codespace for them. I already was using the &lt;a href=&quot;https://github.com/cli/cli&quot;&gt;GitHub CLI&lt;/a&gt; for my automation, so I wanted to use it as well to create the codespace. The documentation states that you can call &lt;a href=&quot;https://cli.github.com/manual/gh_codespace_create&quot;&gt;gh codespace create flags&lt;/a&gt; and you are good to go.&lt;/p&gt;

&lt;p&gt;Well, this command in the CLI works &lt;strong&gt;differently&lt;/strong&gt; then you might think: First of, most of the commands work and pickup the repo that you are currently in. This command does not: you need to pass in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-r&lt;/code&gt; parameter and specify the repo using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;owner/repo&amp;gt;&lt;/code&gt; notation.&lt;/p&gt;

&lt;p&gt;Next up is the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-m&lt;/code&gt; parameter to specify the machine you want to have as the compute option for the Codespace. In the &lt;a href=&quot;https://docs.github.com/en/rest/reference/codespaces&quot;&gt;REST API&lt;/a&gt; you do not need to specify this parameter and it will give you the type of machine in the response:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nl&quot;&gt;&quot;machine&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;standardLinux&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;display_name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;4 cores, 8 GB RAM, 64 GB storage&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;operating_system&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;linux&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;storage_in_bytes&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;68719476736&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;memory_in_bytes&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8589934592&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;cpus&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The help for the CLI command doesn’t give any hints as well.&lt;/p&gt;

&lt;p&gt;So, I tried to be smart and send in this json as the value, thinking they’ve missed that wrapper in the CLI. That fails with no extra information, probably due to some wrong string escaping on my end.&lt;/p&gt;

&lt;p&gt;Then I entered ‘standardLinux’ as the value, thinking this will never work, since I want to create it with a certain &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;size&lt;/code&gt;, and all flavors are Linux based….&lt;/p&gt;

&lt;p&gt;This is then the response:&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; gh codespace create &lt;span class=&quot;nt&quot;&gt;-b&lt;/span&gt; main &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;globaldevopsbootcamp/team01&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;standardLinux&quot;&lt;/span&gt;

error getting machine &lt;span class=&quot;nb&quot;&gt;type&lt;/span&gt;: there is no such machine &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;the repository: standardLinux
Available machines: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;basicLinux32gb standardLinux32gb premiumLinux largePremiumLinux]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Finally some hints of the expected values! (even better: these are the expected values 😄).&lt;/p&gt;

&lt;p&gt;Here you can see how those values match to a machine type:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2022/20220126/20220126_UI_Options.png&quot; alt=&quot;Screen shot of the UI options how they work towards the values from the commandline: they match! The sequence from top to bottom in the UI match the sequence of the command line response&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;tips&quot;&gt;Tips&lt;/h1&gt;
&lt;ol&gt;
  &lt;li&gt;Keep in mind that the API for Codespaces is still in beta.&lt;/li&gt;
  &lt;li&gt;Do note that for automating these API calls you cannot use a GitHub App: the scope for the Apps is not available (yet?). You can make this work with an OAuth App or a Personal Access Token (PAT).&lt;/li&gt;
&lt;/ol&gt;
</content>
		</entry>
	
		<entry>
			<title>Automating my home setup: turning on the lights when the camera is in use</title>
			<link href="https://devopsjournal.io/blog/2022/01/17/Automating-my-presence"/>
			<updated>2022-01-17T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2022/01/17/Automating-my-presence</id>
			<content type="html">&lt;p&gt;I have a nice working from &lt;a href=&quot;/blog/2021/05/13/home-setup&quot;&gt;home setup&lt;/a&gt; that allows me to use a great camera, lights and microphone. I have so many stuff, that I wanted to automate some of it to detect if I am working or not and then toggle them all on or of.&lt;/p&gt;

&lt;p&gt;I already use &lt;a href=&quot;https://www.home-assistant.io/&quot;&gt;Home Assistant&lt;/a&gt; to remotely toggle loads of stuff in the house, so why not integrate everything?&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210513/SetupUpdate2022.jpg&quot; alt=&quot;Photo of home office setup&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;home-assistant-scenes&quot;&gt;Home Assistant Scenes&lt;/h1&gt;
&lt;p&gt;For this I have create some Home assistant scenes and a script to automate these actions. Next I want to toggle based on the state of things on my laptop.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2022/20220117/20220117_homeassistant.png&quot; alt=&quot;Screenshot of home assistant scenes&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;scene-1---office-lights&quot;&gt;Scene 1 - Office lights&lt;/h2&gt;
&lt;p&gt;When this one is triggered, my office lights (small desk lamp), my ‘Do Epic Shit’ signal and my speakers will turn on: everything I need to start working (laptop and monitor have their own flow and can be considered as ‘Always on’). I’ve set this up as with Shelly Plug S and an extension cord that powers all three devices. Wrapped it in a scene in Home Assistant together with my Elgato Light Strip for easy switching it on and off.&lt;/p&gt;

&lt;p&gt;Devices:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Shelly Plug S (extension cord, Speakers, Epic Shit signal)&lt;/li&gt;
  &lt;li&gt;Elgato Light Strip&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;scene-2-camera-on-to-be-automated&quot;&gt;Scene 2: Camera On (to be automated)&lt;/h2&gt;
&lt;p&gt;When the camera is on (I have two different ones to use), I switch on this scene to turn on the 2 Elgato Key Light Airs that I have, so that people can actually see me (check my blogpost on my setup here). When I am done with the call, I switch off the scene and the lights turn off.&lt;/p&gt;

&lt;p&gt;Devices:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Elgato Key Light Air Left&lt;/li&gt;
  &lt;li&gt;Elgato Key Light Air Right&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;scene-3-office-leave&quot;&gt;Scene 3: Office Leave&lt;/h2&gt;
&lt;p&gt;When I stop working, everything I have automated needs to turn off again, so this scene switches of the desk lamp, the Light strip, the ‘Do epic shit’ signal, the speakers and the Key Lights.&lt;/p&gt;

&lt;p&gt;Devices&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Shelly Plug S (extension cord, Speakers, Epic Shit signal)&lt;/li&gt;
  &lt;li&gt;Elgato Light Strip&lt;/li&gt;
  &lt;li&gt;Elgato Key Light Air Left&lt;/li&gt;
  &lt;li&gt;Elgato Key Light Air Right&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;trigger---starting-to-work&quot;&gt;Trigger - Starting to work&lt;/h1&gt;
&lt;p&gt;When I logon to my machine, I want to trigger the Office Lights &amp;amp; Speaker setup (Scene 1). For this I need to send the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Office Lights On&lt;/code&gt; signal to Home Assistant. For this purpose it has a &lt;a href=&quot;https://developers.home-assistant.io/docs/api/rest/&quot;&gt;REST API&lt;/a&gt; that you can configure to securely send the signal to your Home Assistant.&lt;/p&gt;

&lt;p&gt;Since I am running on a Windows Laptop, I can leverage the Windows Task Scheduler to automate this. I have a Windows Task is triggered on ‘workstation unlock’ and run a &lt;a href=&quot;https://github.com/rajbos/home-automation/blob/main/WindowsLogin.ps1&quot;&gt;PowerShell script&lt;/a&gt; that sends the signal to Home Assistant.&lt;/p&gt;

&lt;h5 id=&quot;note-make-sure-to-use-the-pwsh-command-as-the-program-to-start-the-script-otherwise-it-fails-because-of-missing-modulesparameters-on-powershell&quot;&gt;Note: Make sure to use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pwsh&lt;/code&gt; command as the program to start the script, otherwise it fails because of missing modules/parameters on PowerShell&lt;/h5&gt;

&lt;p&gt;To make sure this only runs when I am at home, I configured the Windows Task to only run when the network connection from home is available.&lt;/p&gt;

&lt;p&gt;To make sure this only runs when I am at my office space (that means that my peripherals are plugged in), I have a check in the script to verify if there are more than one monitors available by making this call:&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;Get-CimInstance&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Namespace&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;root\wmi&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-ClassName&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;WmiMonitorBasicDisplayParams&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This prevents the lights from turning on while I am using the laptop at the couch 😁.&lt;/p&gt;

&lt;h1 id=&quot;camera-onoff&quot;&gt;Camera on/off&lt;/h1&gt;
&lt;p&gt;I also want to toggle the lights on or off depending if I am using the camera or not. Since I have multiple camera’s setup and LOADS of video conference applications in use (I always think I have them all, until something obscure pops up), I want to do this automatically &lt;strong&gt;by detecting if a camera is in use&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Looking around you can find solutions like &lt;a href=&quot;https://github.com/isaacrlevin/PresenceLight&quot;&gt;Presence Light&lt;/a&gt;, that only look at the status in the Office365 API. Since I also use different tools, I needed something more.&lt;/p&gt;

&lt;p&gt;Searching around I had to put some things together and I created &lt;a href=&quot;https://github.com/rajbos/home-automation/blob/main/camera-check.ps1&quot;&gt;this PowerShell script&lt;/a&gt; that checks ALL cameras that are connected to the machine and then checks if any process is using them.&lt;/p&gt;

&lt;h2 id=&quot;step-1-find-all-cameras-on-the-machine&quot;&gt;Step 1: Find all camera’s on the machine&lt;/h2&gt;
&lt;p&gt;You can get all the connected devices from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Get-PnpDevice&lt;/code&gt; (plug and play) commandlet. Give it the classes ‘camera’ and ‘image’ to get all devices that can be used as a camera.&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;Get-PnpDevice&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Class&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Camera&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;If you have ever used a camera at a different location (I connect to a standalone camera at our office space for example), you’ll see that those devices are stored there as well.&lt;/p&gt;

&lt;h2 id=&quot;step-2-for-each-camera-see-if-they-are-connected&quot;&gt;Step 2: For each camera, see if they are connected&lt;/h2&gt;
&lt;p&gt;Only for connected camera’s, you get a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Physical Device Object Name&lt;/code&gt; (PDON) back from the call below that you can use to check if the camera is in use. If the result is empty, the device is not connected so you don’t need to check if it is use.&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;Get-PnpDeviceProperty&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-InstanceId&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$device&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;InstanceId&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-KeyName&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;DEVPKEY_Device_PDOName&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This call also returns the &lt;strong&gt;current&lt;/strong&gt; file that can be used to see if a process has a handle on that file. It will look  something like this: ‘\Device\000000ac’. See the logs below for more examples.&lt;/p&gt;

&lt;h2 id=&quot;step-3-check-if-any-process-is-using-the-camera&quot;&gt;Step 3: Check if any process is using the camera&lt;/h2&gt;
&lt;p&gt;That file can be used to check if any process is using the camera by checking the handles. For this you can use the &lt;a href=&quot;https://docs.microsoft.com/en-us/sysinternals/downloads/handle&quot;&gt;handle&lt;/a&gt; tool from SysInternals.&lt;/p&gt;

&lt;h1 id=&quot;tie-it-all-together&quot;&gt;Tie it all together&lt;/h1&gt;
&lt;p&gt;For starting a loop that checks all camera’s and sees if they are in use, I use a Windows Scheduled Task again: If connected to the home wi-fi, when I unlock my laptop, start running a PowerShell script that continuously checks if any camera is in use. If so, turn on the lights. If not, turn them off. I run this check every minute, since finding &lt;strong&gt;all&lt;/strong&gt; handles is a bit of a computational expensive operation.&lt;/p&gt;

&lt;h2 id=&quot;output-example&quot;&gt;Output example&lt;/h2&gt;
&lt;p&gt;Below you can find an example of the output of the script. You can see that it found 5 camera devices:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;the internal camera is in the list twice, no idea why&lt;/li&gt;
  &lt;li&gt;I have two USB camera’s connected&lt;/li&gt;
  &lt;li&gt;The Elgato Facecam is the camera from our office space&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After scanning I wait for the remainder of the minute to not continue check for cameras in use and make the fans fly off 😀.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;13:13:31 Searching for camera devices
13:13:32 Found [5] camera devices
13:13:32 1.  No PDON found for [Elgato Facecam] so skipping it
13:13:32 2.  Checking handles in use for [Logitech BRIO] and PDON [\Device\000000ac]
13:13:38   - Found [2] handles on Logitech BRIO
13:13:38   - Found process [Teams.exe] that has a handle on [Logitech BRIO]
13:13:38 Found active camera device
13:13:38 Running action to make the state [True]
13:13:38 Sleeping for 54 seconds
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;tested-with-these-video-conferencing-applications&quot;&gt;Tested with these video conferencing applications&lt;/h1&gt;
&lt;p&gt;So far I have tested this with the following applications:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Zoom&lt;/li&gt;
  &lt;li&gt;Teams&lt;/li&gt;
  &lt;li&gt;OBS Studio&lt;/li&gt;
  &lt;li&gt;Calls through Edge browser with Teams and Zoom&lt;/li&gt;
  &lt;li&gt;Slack&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;run-with-highest-privileges&quot;&gt;Run with highest privileges&lt;/h2&gt;
&lt;p&gt;For some of these tools the preview window (before you join a meeting) runs in a svchost.exe process. That means you need to run the handle calls with admin privileges. You can set this checkmark in the main window of the Windows Task.&lt;/p&gt;

&lt;h2 id=&quot;prevent-the-powershell-window-from-popping-up&quot;&gt;Prevent the PowerShell window from popping up&lt;/h2&gt;
&lt;p&gt;If you run with the setting ‘Run only when the user is logged on’ on your Windows Task, you’ll see the PowerShell window pop up. To prevent this, check the ‘Run whether the user is logged in or not’ box.&lt;/p&gt;

&lt;h1 id=&quot;repo-with-the-scripts&quot;&gt;Repo with the scripts&lt;/h1&gt;
&lt;p&gt;You can find the &lt;a href=&quot;https://github.com/rajbos/home-automation&quot;&gt;repo&lt;/a&gt; with all my setup on GitHub.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>GitHub Access Tokens explained</title>
			<link href="https://devopsjournal.io/blog/2022/01/03/GitHub-Tokens"/>
			<updated>2022-01-03T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2022/01/03/GitHub-Tokens</id>
			<content type="html">&lt;p&gt;There is a lot of confusion of what GitHub (access) tokens are and how you should use them for automating things inside of GitHub. There are three main types of tokens:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Personal Access Tokens (PATs)&lt;/li&gt;
  &lt;li&gt;The GITHUB_TOKEN environment variable (explainer &lt;a href=&quot;https://youtu.be/RIkqaPKuNFw&quot;&gt;here&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;An access token created from a GitHub App (explainer &lt;a href=&quot;https://youtu.be/xtXnIV20XQw&quot;&gt;here&lt;/a&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can use these tokens to authenticate to GitHub and perform actions with it, like cloning repositories, making API calls, etc.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2022/20220103/james-sutton-FqaybX9ZiOU-unsplash.jpg&quot; alt=&quot;Photo of an old lock&quot; /&gt;&lt;/p&gt;
&lt;h6 id=&quot;photo-by-james-sutton-on-unsplash&quot;&gt;Photo by &lt;a href=&quot;https://unsplash.com/@jamessutton_photography?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;James Sutton&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/s/photos/secure?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/h6&gt;

&lt;h1 id=&quot;1-personal-access-tokens-pats&quot;&gt;1. Personal Access Tokens (PATs)&lt;/h1&gt;
&lt;p&gt;This type of token is often the first thing that people start to use when automating things. It is the same for different CI/CD systems like Azure DevOps that I’ve used before. A &lt;a href=&quot;https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token&quot;&gt;personal access token&lt;/a&gt; is inked to a user account and can be set to automatically expire. These days they are prefixed with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ghp&lt;/code&gt; so that the &lt;a href=&quot;https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning&quot;&gt;secret scanner&lt;/a&gt; can detect them more easily.&lt;/p&gt;

&lt;p&gt;You can use the PAT to do &lt;strong&gt;anything&lt;/strong&gt; the user account that created them can do (as long as it is given the appropriate &lt;a href=&quot;https://docs.github.com/en/developers/apps/building-oauth-apps/scopes-for-oauth-apps#available-scopes&quot;&gt;scopes&lt;/a&gt; for it):&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Create repositories&lt;/li&gt;
  &lt;li&gt;Create issues&lt;/li&gt;
  &lt;li&gt;Create pull requests&lt;/li&gt;
  &lt;li&gt;Read security alerts&lt;/li&gt;
  &lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;the-downsides-of-personal-access-tokens&quot;&gt;The downsides of Personal Access Tokens&lt;/h2&gt;
&lt;p&gt;This token can actually do &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;anything&lt;/code&gt; the user can do, but for &lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;anything&lt;/code&gt;&lt;/strong&gt; the user has access to! If the user has access to repos, organizations or enterprises that have internal/private repos, the PAT has access to it! The only thing you can do is limit the scope it has, but not organizations or repos. (note: this is planned on the &lt;a href=&quot;https://github.com/github/roadmap/issues/184&quot;&gt;roadmap&lt;/a&gt; and very needed).&lt;/p&gt;

&lt;p&gt;Because of this, using a PAT poses a big security risk. Do not hand them out to any random &lt;a href=&quot;/blog/2021/2021/02/06/GitHub-Actions&quot;&gt;action&lt;/a&gt; in your workflows: if they store the PAT somewhere, they can read all your private repos (and more!). I recommend to stay away from using PATs if you can help it.&lt;/p&gt;

&lt;p&gt;Additionally, they are linked to a user, so when that user leaves the company (and the user account is disabled): all automations using their PAT will stop working!&lt;/p&gt;

&lt;p&gt;Note: the only valid reason for using the GitHub Token is for accessing things in the API from the Enterprise level, in case you need to automate things like the creation of users, which should be done with &lt;a href=&quot;https://docs.github.com/en/rest/reference/scim&quot;&gt;SCIM&lt;/a&gt; as a best practice. So still, most of this reason is not valid 😏.&lt;/p&gt;

&lt;h1 id=&quot;2-the-github_token-environment-variable&quot;&gt;2. The GITHUB_TOKEN environment variable&lt;/h1&gt;
&lt;p&gt;The &lt;a href=&quot;https://docs.github.com/en/actions/security-guides/automatic-token-authentication&quot;&gt;GITHUB_TOKEN&lt;/a&gt; is an environment variable you can use in your workflows by injecting it wherever you need it: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$&lt;/code&gt;. The token is autogenerated and is not stored anywhere. It is a token that is only valid for the duration of the workflow it was created for. By default it has read and write access to the repository the workflow is running for. That means you cannot use this token for another repository. You can use it to do things like create issues, create pull requests, or comment on them. Depending on the context, it might get less permissions, for example: when running on a Pull Request coming from a fork, it will only have &lt;strong&gt;read&lt;/strong&gt; access to the repository.&lt;/p&gt;

&lt;p&gt;You can also give it a specific scope by setting that inside the workflow:&lt;/p&gt;
&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;permissions&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;contents&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;read&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;pull-requests&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;write&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This way, the token cannot be used for creating an issue for example, so even if the action you use tries to create an issue, the token doesn’t have the permissions to do so and thus it will fail.&lt;/p&gt;

&lt;p&gt;If you want to learn more on the GITHUB_TOKEN, I have explained its use and how to limit what it can do in this short &lt;a href=&quot;https://www.youtube.com/watch?v=RIkqaPKuNFw&quot;&gt;video&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id=&quot;3-an-access-token-created-from-a-github-app&quot;&gt;3. An access token created from a GitHub App&lt;/h1&gt;
&lt;p&gt;You can use a GitHub App to have very specific access to one or more repositories. I use this for example for setting up a (lot of) Jenkins connections: each team at my customer has their own set of Jenkins jobs that should trigger when someone pushes a new commit to their GitHub repository. By giving each team their own GitHub App, I can limit the access the App has to only the repos that are relevant to them (by installing the App on only their repositories and not all repos in the organization). Since this token is tied to the installation of the App, we call this an &lt;a href=&quot;https://docs.github.com/en/rest/reference/apps#create-an-installation-access-token-for-an-app&quot;&gt;installation token&lt;/a&gt;.&lt;/p&gt;

&lt;h6 id=&quot;note-on-githubcom-you-can-only-create-100-apps-per-organization-on-github-enterprise-server-this-restriction-does-not-apply&quot;&gt;Note: on github.com you can only create 100 apps per organization. On GitHub Enterprise Server this restriction does not apply.&lt;/h6&gt;

&lt;p&gt;With a GitHub App, you get an AppId and a private key in the PEM format. You can create a GitHub App on a &lt;a href=&quot;https://docs.github.com/en/developers/apps/building-github-apps/creating-a-github-app&quot;&gt;website&lt;/a&gt; and then install it on the repositories (or organizations) you want to use it for. You can then use the AppId and the private key to create an access token that can be used to access the repositories.&lt;/p&gt;

&lt;p&gt;There are multiple options to get the token, depending where you want to use it:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Normal shell scripts&lt;/li&gt;
  &lt;li&gt;Use an action&lt;/li&gt;
  &lt;li&gt;Use a library (can be included as an extension to the GitHub CLI)&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;note-this-token-is-only-valid-for-1-hour-after-that-you-need-to-refresh-it-or-your-calls-will-fail&quot;&gt;Note: this token is only valid for 1 hour, after that you need to refresh it or your calls will fail&lt;/h5&gt;

&lt;p&gt;For an explainer of how to create a GitHub App and then use it in for example a GitHub workflow, check my video on it here:&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/xtXnIV20XQw&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;get-an-access-token-for-an-app-with-shell-scripts&quot;&gt;Get an access token for an App with shell scripts&lt;/h2&gt;
&lt;p&gt;I have a &lt;a href=&quot;https://gist.github.com/rajbos/8581083586b537029fe8ab796506bec3&quot;&gt;GitHub Gist&lt;/a&gt; that shows you how to get an access token from a GitHub App from shell scripts (I have an example how to call the shell script from PowerShell as well).&lt;/p&gt;

&lt;p&gt;The steps are:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Get the AppId and the private key from the GitHub App you created&lt;/li&gt;
  &lt;li&gt;Generate a signed JWT token with the AppId and the private key&lt;/li&gt;
  &lt;li&gt;With the JWT toke, get the installations of the app (you need the installation id to create the token)&lt;/li&gt;
  &lt;li&gt;Create a token for the installation&lt;/li&gt;
  &lt;li&gt;Use the token to access the repositories&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can then use this token for any API call you want to make (and that is accessible for an App. Creating codespaces is not for example, since you can only create a codespace for your own user account).&lt;/p&gt;

&lt;p&gt;In the &lt;a href=&quot;https://gist.github.com/rajbos/8581083586b537029fe8ab796506bec3#file-ssh-overwrite-sh&quot;&gt;gist&lt;/a&gt; you also find an example of using git config rewriting to overwrite the global git config with a rule that maps a SSH setup to a HTTPS setup that includes the access token. This can be helpful to help custom scripts / tools that can are preconfigured with only SSH support.&lt;/p&gt;

&lt;h2 id=&quot;get-an-access-token-for-an-app-with-an-action-in-a-workflow&quot;&gt;Get an access token for an App with an action in a workflow&lt;/h2&gt;
&lt;p&gt;Inside of a workflow I always use the &lt;a href=&quot;https://github.com/peter-murray/workflow-application-token-action&quot;&gt;action from Peter Murray&lt;/a&gt;. It takes an application_id and an application_private_key and generates a token that can be used to access the repositories. You can even give the token it creates a scope by sending in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;permissions&lt;/code&gt; parameter.&lt;/p&gt;

&lt;h2 id=&quot;get-an-access-token-for-an-app-with-a-library&quot;&gt;Get an access token for an App with a library&lt;/h2&gt;
&lt;p&gt;My buddy &lt;a href=&quot;https://github.com/Link-/&quot;&gt;Bassem Dghaidi&lt;/a&gt; has created a bash library that does the work for you, and can be included in the &lt;a href=&quot;https://github.com/cli/cli&quot;&gt;GitHub CLI&lt;/a&gt;. You can find it in this &lt;a href=&quot;https://github.com/Link-/gh-token&quot;&gt;repo&lt;/a&gt; with all the setup needed. You can for example use this easily in a Docker container when you are automating things.&lt;/p&gt;

&lt;h5 id=&quot;note-for-installation-as-an-extension-in-the-cli-you-need-an-authenticated-cli-session-first-so-that-is-not-helpful-for-automation-purposes&quot;&gt;Note: for installation as an extension in the CLI you need an authenticated CLI session first, so that is not helpful for automation purposes.&lt;/h5&gt;

&lt;h2 id=&quot;the-downside-of-using-github-apps&quot;&gt;The downside of using GitHub Apps&lt;/h2&gt;
&lt;p&gt;GitHub Apps are great for automating things: they have more access then the GITHUB_TOKEN (across repositories for example) and do not have the issue that they operate from a user account and have access to &lt;strong&gt;everything&lt;/strong&gt; the user has access to (like a PAT).&lt;/p&gt;

&lt;p&gt;There are some downsides as well:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;‘Only 100 apps’ per organization on github.com (so public repos or GitHub Enterprise Cloud: GHEC).&lt;/li&gt;
  &lt;li&gt;Only minimal options to automate the setup of the apps themselves: you can use a &lt;a href=&quot;/blog/2021/2021/12/27/GitHub-App-from-manifest&quot;&gt;manifest&lt;/a&gt; to create them, but installing them on repositories (even the ones you own), is not possible with an API.&lt;/li&gt;
  &lt;li&gt;Little to no support on external tools: most of them want to have the token and cannot generate that token from the AppId and the private key, so you need to do that yourself (and be aware of the 1 hour expiration!).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;some-things-i-have-used-github-apps-for&quot;&gt;Some things I have used GitHub Apps for:&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;Installing and configuring self hosted runners.&lt;/li&gt;
  &lt;li&gt;Configuring access from Jenkins pipelines to GitHub repos.&lt;/li&gt;
  &lt;li&gt;Inside workflows everywhere: I like the limited access the App has and do not want to give anything my PAT as it has way to much access!&lt;/li&gt;
  &lt;li&gt;Automating creation of repos with defined content for Global DevOps Bootcamp.&lt;/li&gt;
&lt;/ul&gt;
</content>
		</entry>
	
		<entry>
			<title>Create a GitHub App from a manifest</title>
			<link href="https://devopsjournal.io/blog/2021/12/27/GitHub-App-from-manifest"/>
			<updated>2021-12-27T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2021/12/27/GitHub-App-from-manifest</id>
			<content type="html">&lt;p&gt;At my customer we have the need to create a lot of GitHub Apps. In this specific case we use &lt;a href=&quot;https://docs.github.com/en/developers/apps/building-github-apps/creating-a-github-app&quot;&gt;GitHub Apps&lt;/a&gt; as an integration point between GitHub and Jenkins: the code is moving to GitHub, and we still want to trigger our existing Jenkins jobs on code changes. We have over a 100 teams in Jenkins, all with their own pipelines. We have a security requirement that teams that connect to their code in a Jenkins pipeline only can see their own code, and not the repos from other teams. That means that each team has to have its own GitHub credential set up for them.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20211227/hello-i-m-nik-kLq9cLl5vbs-unsplash.jpg&quot; alt=&quot;Image of a LEGO figurine dressed in a space suite with a happy expression&quot; /&gt;&lt;/p&gt;
&lt;h6 id=&quot;photo-by-hello-im-nik-on-unsplash&quot;&gt;Photo by &lt;a href=&quot;https://unsplash.com/@helloimnik?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Hello I’m Nik&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/collections/3589562/robot-like-figures?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/h6&gt;

&lt;p&gt;I consider it a &lt;a href=&quot;/blog/2022/01/03/GitHub-Tokens&quot;&gt;bad practice&lt;/a&gt; to use Personal Access Tokens (they have way to much scope, which should improve &lt;a href=&quot;https://github.com/github/roadmap/issues/184&quot;&gt;in the future&lt;/a&gt;). Instead we use a GitHub App: we can create and install the GitHub App the organization level and give it access to certain repositories. This way we can use the GitHub App to trigger our Jenkins jobs and give it only access to the repositories it need access to.&lt;/p&gt;

&lt;p&gt;At the moment there are no API’s in GitHub to handle this situation for you: you have to create the App manually, get the credentials and install it in the organization for the repositories that you need the App to have access to. Creating the App has quite a few steps, like setting up a name and a description, adding webhook to trigger, configuring all the permissions you need and the events that belong to those permissions.&lt;/p&gt;

&lt;p&gt;Given that we have a lot of teams, this is a repeating task and prone to errors when you follow the internal documentation for setting this up. To improve this, we have created a &lt;a href=&quot;https://docs.github.com/en/developers/apps/building-github-apps/creating-a-github-app-from-a-manifest&quot;&gt;manifest&lt;/a&gt; that we can use to create the App.&lt;/p&gt;
&lt;h5 id=&quot;note-you-can-also-do-the-same-with-a-request-with-request-parameters&quot;&gt;Note: you can also do the same with a request with &lt;a href=&quot;https://docs.github.com/en/developers/apps/building-github-apps/creating-a-github-app-using-url-parameters&quot;&gt;request parameters&lt;/a&gt;.&lt;/h5&gt;

&lt;h2 id=&quot;flow&quot;&gt;Flow&lt;/h2&gt;
&lt;p&gt;With a manifest, we can use a flow in an internal webapplication that will create the App with all the default settings, get the credentials and store the credentials somewhere safe. Since this flow requires a user to be logged in, we need to give them a webpage that set things up (the manifest) and then redirect to the page on our GitHub environment (Can be Enterprise Cloud or Enterprise Server), that will do the authentication and authorization to check if the user has the rights to setup the App. Unfortunately that is also as far as we can go, since there are no API’s to install the App into repositories.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20211227/20211227_Flow.png&quot; alt=&quot;Overview of the steps in the creation flow&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In the overview above we can see that there are six steps to create the App with a manifest:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Send the user to our index page where we setup the manifest (we let them pick the environment this app needs to be created in) and redirect to the GitHub App creation page with the manifest.&lt;/li&gt;
  &lt;li&gt;GitHub handles the authentication and authorization.&lt;/li&gt;
  &lt;li&gt;Redirects the user back to our redirect page (you pass this in with the manifest).&lt;/li&gt;
  &lt;li&gt;Our redirect page loads a specific code from the redirect (this code is only valid for 1 hour).&lt;/li&gt;
  &lt;li&gt;And posts that code back to GitHub to get the App information it just created.&lt;/li&gt;
  &lt;li&gt;We store the AppId and the private key (PEM) for the App somewhere safe, so we can continue with the next steps.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Example files for this flow can be found &lt;a href=&quot;https://github.com/rajbos/create-github-app-from-manifest&quot;&gt;here&lt;/a&gt;. You need to host them on your own server, but redirecting to localhost works just fine. (next step for us will be hosting that in a NodeJS app that can run either locally or be hosted in a Docker container). It also includes a &lt;a href=&quot;https://www.youtube.com/watch?v=PAR22TjG6Wg&quot;&gt;5 minute video&lt;/a&gt; that explains the whole flow.&lt;/p&gt;

&lt;h2 id=&quot;next-steps&quot;&gt;Next steps:&lt;/h2&gt;
&lt;p&gt;Setting up the manifest is straightforward, but only part of the setup we need. To be able to configure this on the client side (in this case Jenkins), we need to go through the following steps:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Install the new App in the repositories it needs to have access to (you give it access to those repos from the org level).&lt;/li&gt;
  &lt;li&gt;Add then new set of Credentials (AppId + private key) in Jenkins at the correct folder / team level&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;future-work&quot;&gt;Future work:&lt;/h2&gt;
&lt;p&gt;Following DevOps best practices, we want to setup key rotation for the app’s as well. To do this there are also no API’s available at the moment, and when you generate a new private key, the old one is revoked immediately 😒.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Maturity levels of using GitHub Actions Securely</title>
			<link href="https://devopsjournal.io/blog/2021/12/11/GitHub-Actions-Maturity-Levels"/>
			<updated>2021-12-11T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2021/12/11/GitHub-Actions-Maturity-Levels</id>
			<content type="html">&lt;p&gt;I’ve been discussing using GitHub Actions in a secure way for a while now (see &lt;a href=&quot;/blog/2021/10/27/GitHub-Universe-Session&quot;&gt;here&lt;/a&gt;), and I got a question on how to improve your usage of actions. I wanted to capture that info in an easy to follow set of steps, so here we go:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Default demo examples: version pinning or by branch&lt;/li&gt;
  &lt;li&gt;Review the source code and trust the publisher / action&lt;/li&gt;
  &lt;li&gt;SHA hashes&lt;/li&gt;
  &lt;li&gt;Dependabot for actions&lt;/li&gt;
  &lt;li&gt;Fork the repo and take control&lt;/li&gt;
  &lt;li&gt;github-fork-updater&lt;/li&gt;
  &lt;li&gt;Internal marketplace&lt;/li&gt;
  &lt;li&gt;Request actions process&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20211211/markus-spiske-71uUjIt3cIs-unsplash.jpg&quot; alt=&quot;Image of small green plants that just broke through the soil&quot; /&gt;&lt;/p&gt;
&lt;h5 id=&quot;photo-by-markus-spiske-on-unsplash&quot;&gt;Photo by &lt;a href=&quot;https://unsplash.com/@markusspiske?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Markus Spiske&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/s/photos/maturity?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/h5&gt;

&lt;p&gt;I’ll go over each of these below and give some more context. With that you should be able to determine where you or your company is at this scale. I’d love to what your next step will be, so please let me know on &lt;a href=&quot;https://www.linkedin.com/in/bosrob&quot;&gt;LinkedIn&lt;/a&gt;!&lt;/p&gt;

&lt;h2 id=&quot;1-default-demo-examples-version-pinning-or-by-branch&quot;&gt;1) Default demo examples: version pinning or by branch&lt;/h2&gt;
&lt;p&gt;This is currently where all the demos start: use version pinning (now required) or by branch:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v1&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@main&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The engine and UI now force you to use one of these ways, or fail to start the workflow or save it.&lt;/p&gt;

&lt;p&gt;The first line is referencing a version which was used to publish the action as a release. You can find all the released version in the action repository itself: &lt;a href=&quot;https://github.com/actions/checkout/releases&quot;&gt;link&lt;/a&gt;. Often the release will have release notes and a list of the commits that made it to the new release. Actions should be following &lt;a href=&quot;https://semver.org/&quot;&gt;semantic versioning&lt;/a&gt; which means that you can use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v2&lt;/code&gt; to always use the latest compatible version with v2. So if the publisher releases v2.14.17, the runner will use that version.&lt;/p&gt;

&lt;p&gt;The second line references a specific branch in the actions’ repository.&lt;/p&gt;

&lt;p&gt;For both options, the runners will download the entire repository by calling either git checkout (if git is installed on the runner) or downloading the status of the repository as a tarball. The &lt;a href=&quot;https://github.com/actions/runner&quot;&gt;runner is open source&lt;/a&gt;, so you can follow along with the steps it is taking.&lt;/p&gt;

&lt;p&gt;The issue with using both, is that you are pulling in arbitrary code from the internet! Even if you follow &lt;a href=&quot;/blog/2021/02/06/GitHub-Actions&quot;&gt;best practices&lt;/a&gt;, you should look at what the action is doing for you. GitHub has no documented process for publishing an action or a security check on them: anyone can set up a public repository with the right content and then everyone can use it. Very helpful to get started fast, but security is not part of that picture!&lt;/p&gt;

&lt;h3 id=&quot;why-is-this-first-level-so-bad&quot;&gt;Why is this first level so bad?&lt;/h3&gt;
&lt;p&gt;As I said, the methods above always use a version of the action as is. The branching method uses whatever was pushed last to that branch. So even if you have just reviewed it and start using it, a newer version might just have been pushed to that branch. This happens &lt;strong&gt;without you knowing of&lt;/strong&gt; it at all! Anything can happen between you reviewing it and you using it. A vulnerability in a package the action is using might be found, or the maintainer decides to export all environment variables to their own server. Or maybe even the maintainer hands over the code to a third party so they can do the maintenance. You never know, but might be using an action in production that is not what you intended.&lt;/p&gt;

&lt;p&gt;For version pinning the same principle applies: you might even pin a specific version, say v2.1.4 and think you’re now safe: you are not! The version of a release comes from a &lt;a href=&quot;https://git-scm.com/book/en/v2/Git-Basics-Tagging&quot;&gt;git tag&lt;/a&gt;. Git tags can be reused! They are flexible by design, so the maintainer can tag code and push that as v2.1.4. Then, even months later decide to add some code, maybe introduce a vulnerability and reuse the same tag! Then the runner will download the version of the repository &lt;strong&gt;as it is with that tag&lt;/strong&gt; linked to it and you are running code in your workflow that you never intended.&lt;/p&gt;

&lt;h2 id=&quot;2-review-the-source-code-and-trust-the-publisher--action&quot;&gt;2) Review the source code and trust the publisher / action&lt;/h2&gt;
&lt;p&gt;The first step in being better at using actions in a secure way is to review the source code: know what the action will be doing on your behalf! The repo is open source, so go to the repo:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20211211/20211211_Marketplace.png&quot; alt=&quot;Screenshot of where to find the action repository link&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Did you know you can even use the action call it self to find the repo? The uses statement can be partially copied behind github.com to have the direct link to the repo! So this &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uses: actions/checkout@v1&lt;/code&gt; becomes this &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github.com/actions/checkout&lt;/code&gt;!&lt;/p&gt;

&lt;p&gt;Now find the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;action.yml&lt;/code&gt; in the root and find it’s entry point under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runs&lt;/code&gt;, for example the checkout action:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;runs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;using&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;node12&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;dist/index.js&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;dist/index.js&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;using&lt;/code&gt; indicates that this action runs under node, so it is JavaScript based. This can be compiled TypeScript, so the next step is to look for the entry point and how it got there. Often you find a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src&lt;/code&gt; folder and the TypeScript files in there (*.ts). Usually the starting file is then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main.ts&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;index.ts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In this case we see a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt; property that indicates the starting point when this action starts, so &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;index.js&lt;/code&gt; in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dist&lt;/code&gt; folder. This already is a good chance this action is build with TypeScript. You often can find where it actually starts by checking the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;packages.json&lt;/code&gt; file. For this action we find &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;main&quot;: &quot;lib/main.js&quot; in there, so we can find where it actually starts 😄. For TypeScript the file names just have the &lt;/code&gt;.ts` extension.&lt;/p&gt;

&lt;p&gt;We also have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;post&lt;/code&gt; definition here, which means that part of the action will also run at the end of a run:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20211211/20211211_PostStep.png&quot; alt=&quot;Screenshot of the post step execution&quot; /&gt;&lt;/p&gt;

&lt;p&gt;To summarize, there is no verification or security label before someone can use an action, so you need to do your own due diligence!
Check what the action is doing and make an informed decision if you can trust the action.&lt;/p&gt;

&lt;h2 id=&quot;3-sha-hashes&quot;&gt;3) SHA hashes&lt;/h2&gt;
&lt;p&gt;Now that you have reviewed the actions’ source code, you need to make sure you always use that specific version of the action. This is where the SHA hashes come in. Each git commit gets its own unique SHA hash, based on the contents of the commit. That SHA hash is unique! Generating the same SHA hash with different contents is possible, but very hard to do. Having the contents of the repo as a pre-specified setup (with an ‘action.yml’ file for example), makes it very unlikely that you will get the same SHA hash for different code (we call that SHA collisions).
That means we can use the SHA hash to indicate a specific version of the action we will use. The runner will detect the SHA hash and use that to checkout the actions repository at runtime. Since adding or changing the code in the repo will mean a new SHA hash, you will always use the version you have reviewed.&lt;/p&gt;

&lt;p&gt;You can find the SHA hashes in the history of the repo, by going to the commit history. Don’t use the short version of the hash, that was insecure (collisions more likely) and does no longer work.
&lt;img src=&quot;/images/2021/20211211/20211211_SHA_short.png&quot; alt=&quot;Screenshot of the GitHub UI on the repo page, highlighting the last commit SHA in the update banner&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You can then find the full SHA hash here:
&lt;img src=&quot;/images/2021/20211211/20211211_SHA_complete.png&quot; alt=&quot;Screenshot of the GitHub UI highlighting the entire commit SHA on the commit information page&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;4-dependabot-for-actions&quot;&gt;4) Dependabot for actions&lt;/h2&gt;
&lt;p&gt;Dependabot also supports actions as an ecosystem. It can run on a schedule and check all workflows in your repository for the actions you use. If you use version 2.1.5 of an action and the publisher has released version 2.1.6, Dependabot will create a Pull Request for you that updates the action to the latest version (major/minor updates configurable).&lt;/p&gt;

&lt;p&gt;Dependabot supports all usage options for the actions that indicate a version:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;owner/action@pinned-version&lt;/li&gt;
  &lt;li&gt;owner/action@SHA-hash&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;updates&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;package-ecosystem&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;github-actions&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;directory&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;.github/workflows/&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;schedule&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# Check for updates to GitHub Actions every weekday&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;interval&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;weekly&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The only downside is that in the Dependabot Pull Request, you only see the version number (or SHA hash) change in the PR. If the publisher has added commits or release notes, that will be available on the ‘Conversation’ tab of the PR. It’s up to you to check the actual code changes!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20211211/20211211_DependabotPR.png&quot; alt=&quot;Screenshot of dependabot PR&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;5-fork-the-repo-and-take-control&quot;&gt;5) Fork the repo and take control&lt;/h2&gt;
&lt;p&gt;Now that you have at least a safer way of using actions (by reviewing their code and pinning it to the version you have reviewed), you are ready for the next step: taking full control over the action and any updates to them. This is one of the &lt;a href=&quot;/blog/2021/02/06/GitHub-Actions-Forking-Repositories&quot;&gt;best practices&lt;/a&gt; for using actions and was always the initial guidance for using them: fork the repo!&lt;/p&gt;

&lt;p&gt;By forking the action you have a full copy of it, so in case something happens (newer versions, or the maintainer deletes the repo), you workflows will continue to do their work happily. You are now also in control of any incoming updates. More on that in the next level. I always use an organization called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;org-name-actions&lt;/code&gt; for this, so that I have a single location for all the actions we use and an easy way to limit the use of ALL public actions (see next paragraph). Especially in an work setting, you do not want your GitHub users to just run &lt;em&gt;any&lt;/em&gt; action from the public internet, you need to check these things first!&lt;/p&gt;

&lt;p&gt;The nice thing is that you now can enforce that the users in your organization can &lt;strong&gt;only&lt;/strong&gt; use the actions under your control. Go to your organization –&amp;gt; permissions –&amp;gt; actions permissions and select ‘Allow select actions’:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20211211/20211211_Settings.png&quot; alt=&quot;Screenshot of the settings&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You can now enter the actions you want to allow for the selected repositories in your organization (you can also do this on the repo level). If someone triggers a workflow that does not adhere to these limitations, it will give an error and &lt;strong&gt;will not start&lt;/strong&gt;. No action repos will even be downloaded on a runner.&lt;/p&gt;

&lt;p&gt;You can go completely crazy in this list:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Setting&lt;/th&gt;
      &lt;th&gt;Description&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;owner/*&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;All actions from this owner are allowed&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;owner/action@*&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The action in the repo ‘action’ from this owner are allowed for all versions and all branches&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;owner/action@main&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The main branch of this action is allowed&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;owner/action@v2&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;All versions for v2 are allowed for this action of this owner&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;owner/action@SHA&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Only this version is allowed for this action of this owner&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h4 id=&quot;note-owner-can-be-either-an-organization-or-a-user-account-here&quot;&gt;Note: ‘owner’ can be either an organization or a user account here.&lt;/h4&gt;

&lt;h2 id=&quot;6-github-fork-updater&quot;&gt;6) github-fork-updater&lt;/h2&gt;
&lt;p&gt;Now that you have a fork of the repo, you need to maintain this fork. The best way of doing that is sharing any changes you make back to the publisher of the action (yeah for Open Source) by clearly communicating what you changed and why. Then send in a Pull Request.&lt;/p&gt;

&lt;p&gt;You also want to incorporate incoming changes from the publisher back into the fork. You can wait until someone spots the message on the repo that says ‘you are x commits behind the parent repo’, but that doesn’t scale of course. There is also a big button on that banner that lets you pull in all the updates automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DO NOT USE THAT BUTTON!&lt;/strong&gt; Use the compare in the drop down first!&lt;/p&gt;

&lt;p&gt;You’d be blindly pulling all the changes from the parent repo! I think the explanation from level 2 where clear: review what the action is doing for you! That means you need to check the incoming changes!&lt;/p&gt;

&lt;p&gt;To scale this process, I have created the &lt;a href=&quot;https://github.com/rajbos/github-fork-updater&quot;&gt;github-fork-updater&lt;/a&gt; repository that centralizes the process for you. It checks all forks on a schedule and will create an issue for you when there are updates. You can then review the incoming changes and decide to update the fork. More is explained in this &lt;a href=&quot;/blog/2021/02/06/GitHub-Actions-Forking-Repositories&quot;&gt;blogpost&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;7-internal-marketplace&quot;&gt;7) Internal marketplace&lt;/h2&gt;
&lt;p&gt;The next maturity level is having a setup to let the users in your GitHub organization find the actions you have available in your actions-org. They can search the organization of course with the normal search options, but that means searching in all code in the repos, trying to find something that the action should do. You only want to search in the action.yml and the readme. Having a better search experience therefor is a nice way to send your users to a central location: the &lt;a href=&quot;https://github.com/rajbos/actions-marketplace&quot;&gt;internal marketplace&lt;/a&gt;. Having all that data in a single location also has additional benefits. More on that later.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20211014/20211014_Marketplace.png&quot; alt=&quot;Screenshot of the internal marketplace website&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The internal marketplace groups all your (internal/private or public) actions in one place, with the information from the action.yml and the readme for users to search on. That way you can send your users here to find the actions already approved within your organization. Read more on the internal marketplace &lt;a href=&quot;/blog/2021/10/14/GitHub-Actions-Internal-Marketplace&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h4 id=&quot;note-this-is-still-work-in-progress-one-of-the-things-i-still-want-to-add-is-adding-links-to-the-internal-usages-of-the-actions-in-case-a-vulnerability-is-found-or-for-implementation-examples&quot;&gt;Note: this is still work in progress, one of the things I still want to add is adding links to the internal usages of the actions in case a vulnerability is found or for implementation examples.&lt;/h4&gt;

&lt;h2 id=&quot;8-request-actions-process&quot;&gt;8) Request actions process&lt;/h2&gt;
&lt;p&gt;The last maturity level is setting up a good governance process to add actions to the internal marketplace. More information can be found in my blogpost on it &lt;a href=&quot;/blog/2021/10/14/GitHub-Actions-Internal-Marketplace&quot;&gt;here&lt;/a&gt;. We have created a &lt;a href=&quot;https://github.com/rajbos/github-actions-requests&quot;&gt;repo&lt;/a&gt; for it where we can go and:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Users create an issue to request a public action to be added to the internal marketplace&lt;/li&gt;
  &lt;li&gt;An engineer with a security mindset (and training!) does a preliminary manual check on the actions’ source code.&lt;/li&gt;
  &lt;li&gt;The engineer can request an automated scan&lt;/li&gt;
  &lt;li&gt;The scan forks the action over to a test organization and enables Dependabot for the vulnerability alerts&lt;/li&gt;
  &lt;li&gt;Runs a CodeQL scan&lt;/li&gt;
  &lt;li&gt;Checks the actions repo for its own Dependabot and CodeQL setup&lt;/li&gt;
  &lt;li&gt;Runs a Trivy scan on any containers in use&lt;/li&gt;
  &lt;li&gt;Drops the results of the scans back into the request issue&lt;/li&gt;
  &lt;li&gt;The engineer can make an informed decision to decide if they want to take on the risk of using the action&lt;/li&gt;
  &lt;li&gt;On approval, the action gets automatically forked over to the org-actions organizations, where it will be useable and be picked up by the internal marketplace&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;summary&quot;&gt;Summary&lt;/h1&gt;
&lt;p&gt;The eight maturity levels is something that each action user hopefully goes through when they are told about the risks of using actions out of the box. For more insights on using actions with security in mind you can watch my &lt;a href=&quot;/blog/2021/10/27/GitHub-Universe-Session&quot;&gt;GitHub Universe session&lt;/a&gt; on this exact topic.&lt;/p&gt;

&lt;p&gt;I’d love to hear where you are on the maturity scale and how you intent to improve you security: let me know on Twitter or LinkedIn!&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>dev.to entry: Using GitHub Actions to setup a Marketplace</title>
			<link href="https://devopsjournal.io/blog/2021/12/04/github-actions-marketplace"/>
			<updated>2021-12-04T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2021/12/04/github-actions-marketplace</id>
			<content type="html">&lt;p&gt;This post is for sharing my dev.to entry for the &lt;a href=&quot;https://dev.to/devteam/join-us-for-the-2021-github-actions-hackathon-on-dev-4hn4&quot;&gt;2021 GitHub Actions Hackathon&lt;/a&gt;. This entry shows how I have setup the workflow(s) for the &lt;a href=&quot;https://devopsjournal.io/actions-marketplace/&quot;&gt;GitHub Actions Marketplace&lt;/a&gt;. I wanted to have a long form post detailing the steps and reasoning behind each the setup as an entry point for people building these automations.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20211204/20211204_dev_to_hackathon.jpg&quot; alt=&quot;GitHub + dev hackathon banner&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;workflow&quot;&gt;Workflow:&lt;/h2&gt;
&lt;p&gt;The starting workflow for this setup can be found &lt;a href=&quot;https://github.com/rajbos/actions-marketplace/blob/main/.github/workflows/get-action-data.yml&quot;&gt;here&lt;/a&gt;. This workflow goes through all repositories in a user or organization and checks if they contain action definitions. If so, it adds the information about the action and the repository it lives in to a data file that then can be used. In this case the data is used to display an Internal GitHub Action Marketplace so our organization users. We lock our main organization down so that only the actions in our internal marketplace can be used as is a common &lt;a href=&quot;/blog/2021/02/06/GitHub-Actions&quot;&gt;best practice&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;get-action-data&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;devops-actions/load-available-actions@v1.2.12&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Load available actions&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;load-actions&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;PAT&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ secrets.PAT }}&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ github.repository_owner }}&lt;/span&gt;

    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Store json file&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;echo &apos;${{ steps.load-actions.outputs.actions }}&apos; &amp;gt; &apos;actions-data.json&apos;&lt;/span&gt;

    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Upload result file as artefact&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/upload-artifact@v2&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions-data.json&lt;/span&gt;

    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Upload json to this repository&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;rajbos-actions/github-upload-action@v0.2.0&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;access-token&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ secrets.PAT }}&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;file-path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions-data.json&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;owner&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ github.repository_owner }}&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;repo&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions-marketplace&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;branch-name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;gh-pages&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;workflow-steps&quot;&gt;Workflow steps&lt;/h2&gt;
&lt;p&gt;Below I listed the steps in the workflow that do the actual work.&lt;/p&gt;

&lt;h3 id=&quot;load-available-actions-from-an-organization&quot;&gt;Load available actions from an organization&lt;/h3&gt;
&lt;p&gt;I created the &lt;a href=&quot;https://github.com/devops-actions/load-available-actions&quot;&gt;load-available-actions&lt;/a&gt; action just for this purpose. It needs a token to access the GitHub API and will load all repositories it can find from either the user or the organization you give it. It loops through all the repos, scans for either an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;action.yml&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;actions.yaml&lt;/code&gt; in the root of the repository and adds the information to the output of this step so that it can be used later on in the workflow.&lt;/p&gt;

&lt;h3 id=&quot;store-the-outcome-of-the-previous-step-as-json&quot;&gt;Store the outcome of the previous step as json&lt;/h3&gt;
&lt;p&gt;We store the output of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;load-available-actions&lt;/code&gt; action in a file so we can upload it more easily to both the artefacts for this run (handy for testing and validation) and to the GitHub Actions Marketplace repo.&lt;/p&gt;

&lt;h3 id=&quot;upload-the-json-as-an-artefact-for-easy-testing&quot;&gt;Upload the json as an artefact for easy testing&lt;/h3&gt;
&lt;p&gt;To view the results in the workflow run, I upload the json file as an artefact so I can download and check it if needed.&lt;/p&gt;

&lt;h3 id=&quot;upload-the-json-to-the-github-actions-marketplace&quot;&gt;Upload the json to the GitHub Actions Marketplace&lt;/h3&gt;
&lt;p&gt;This is the important step for later usage of the data: store it inside the repository that holds the Internal GitHub Marketplace.
For this I use this &lt;a href=&quot;https://github.com/LasyIsLazy/github-upload-action&quot;&gt;GitHub Action&lt;/a&gt; where I have added the option to upload the files into a branch: Since the GitHub Pages website from this repo runs on a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gh-pages&lt;/code&gt; branch, I needed to upload the file in the same branch.&lt;/p&gt;

&lt;h4 id=&quot;open-source-ftw&quot;&gt;Open Source FTW!&lt;/h4&gt;
&lt;p&gt;Actions have to be Open Source to be able to use them by default (you can use private repos, but that is a bit more work), So if you miss something, you can start a conversation with the maintainer (use issues or discussions if they have that enabled) and even send in a &lt;a href=&quot;https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests&quot;&gt;Pull Request&lt;/a&gt; to propose the fix! The upload-action was missing some features that I needed for this project. I have added the following features:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/LasyIsLazy/github-upload-action/issues/2&quot;&gt;Support upload to a branch&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/LasyIsLazy/github-upload-action/issues/3&quot;&gt;Support GHES&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;internal-github-actions-marketplace&quot;&gt;Internal GitHub Actions Marketplace&lt;/h1&gt;
&lt;p&gt;You can read more information about the Internal GitHub Actions Marketplace I created in this &lt;a href=&quot;/blog/2021/10/14/GitHub-Actions-Internal-Marketplace&quot;&gt;blog post&lt;/a&gt;. The goal is to give our users a place where they can find the supported actions internally, since we block the use of public actions in our production organization.
&lt;img src=&quot;/images/2021/20211014/20211014_Marketplace.png&quot; alt=&quot;Image of the Actions Marketplace&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;next-steps&quot;&gt;Next steps:&lt;/h2&gt;
&lt;p&gt;Next to prettifying the marketplace and adding search, I want to add more information about the actions being used inside of our production environment. This way my users can see examples of their usages and I can easily find the implementations in case there is something wrong with the action, like a security vulnerability or newer features being implemented.&lt;/p&gt;

&lt;p&gt;Loading the information is done by another action that loads all repositories’ workflow files and outputs a list of all the actions in use in them, with the information of which workflow uses with action and version of it. The action can be found in &lt;a href=&quot;https://github.com/devops-actions/load-used-actions&quot;&gt;devops-actions/load-used-actions&lt;/a&gt;.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>GitHubs magic files</title>
			<link href="https://devopsjournal.io/blog/2021/11/26/GitHub-magic-files"/>
			<updated>2021-11-26T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2021/11/26/GitHub-magic-files</id>
			<content type="html">&lt;p&gt;I keep coming across files in GitHub that have some mystic magic feeling to them. There’s always a small incantation to come with them: the have to have the right name, the right extension &lt;em&gt;and&lt;/em&gt; have to be stored in the right directory. I wanted to have an overview of all these spells for myself, so here we are 😉.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20211126/20211126-github-magic-files.jpg&quot; alt=&quot;Photo of a cauldron with a person pointing a want to it, mist coming out of the cauldron&quot; /&gt;&lt;/p&gt;
&lt;h6 id=&quot;photo-by-artem-maltsev-on-unsplash&quot;&gt;Photo by &lt;a href=&quot;https://unsplash.com/@art_maltsev?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Artem Maltsev&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/s/photos/magic?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/h6&gt;

&lt;h1 id=&quot;overview&quot;&gt;Overview&lt;/h1&gt;
&lt;p&gt;A list of all the magic files / links that I came across in GitHub. I also created a LinkedIn Learning Course for ~25 of these files, with more detail how to use them. You can find that course on &lt;a href=&quot;https://www.linkedin.com/learning/25-github-configuration-files-you-should-be-using&quot;&gt;LinkedIn Learning&lt;/a&gt;.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Filename&lt;/th&gt;
      &lt;th&gt;Location&lt;/th&gt;
      &lt;th&gt;.github repo support&lt;/th&gt;
      &lt;th&gt;Description&lt;/th&gt;
      &lt;th&gt;Docs&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;CNAME&lt;/td&gt;
      &lt;td&gt;root&lt;/td&gt;
      &lt;td&gt;no&lt;/td&gt;
      &lt;td&gt;Alias for the GitHub Pages site&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/managing-a-custom-domain-for-your-github-pages-site&quot;&gt;Docs&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;CONTRIBUTING.md&lt;/td&gt;
      &lt;td&gt;root, /docs or /.github&lt;/td&gt;
      &lt;td&gt;yes&lt;/td&gt;
      &lt;td&gt;How to contribute to a project&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.github.com/en/communities/setting-up-your-project-for-healthy-contributions/setting-guidelines-for-repository-contributors&quot;&gt;Guidelines&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;CODE_OF_CONDUCT.md&lt;/td&gt;
      &lt;td&gt;root, /docs or /.github&lt;/td&gt;
      &lt;td&gt;yes&lt;/td&gt;
      &lt;td&gt;Code of conduct&lt;/td&gt;
      &lt;td&gt;How to behave for this project &lt;a href=&quot;https://docs.github.com/en/communities/setting-up-your-project-for-healthy-contributions/adding-a-code-of-conduct-to-your-project&quot;&gt;Code of Conduct&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;CODEOWNERS&lt;/td&gt;
      &lt;td&gt;root, /docs or /.github&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;List of people who can make changes to the files or folders&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners&quot;&gt;Code owners info&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;CITATION.cff, CITATION.md, and others&lt;/td&gt;
      &lt;td&gt;root or inst/CITATION&lt;/td&gt;
      &lt;td&gt;no&lt;/td&gt;
      &lt;td&gt;Let others know how to citate your work&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-citation-files&quot;&gt;cff&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;LICENSE or LICENSE.md or LICENSE.txt or LICENSE.rst&lt;/td&gt;
      &lt;td&gt;root&lt;/td&gt;
      &lt;td&gt;no&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/licensing-a-repository&quot;&gt;License&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;FUNDING.yml&lt;/td&gt;
      &lt;td&gt;.github folder&lt;/td&gt;
      &lt;td&gt;yes&lt;/td&gt;
      &lt;td&gt;Display a Sponsor button in your repo and send people to platforms where they can fund your development&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/displaying-a-sponsor-button-in-your-repository&quot;&gt;Docs&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;SECURITY.md&lt;/td&gt;
      &lt;td&gt;root, .github or docs folder&lt;/td&gt;
      &lt;td&gt;yes&lt;/td&gt;
      &lt;td&gt;Instructions for how to report a security vulnerability&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.github.com/en/code-security/getting-started/adding-a-security-policy-to-your-repository&quot;&gt;Security policy&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;SUPPORT.md&lt;/td&gt;
      &lt;td&gt;root, .github or docs folder&lt;/td&gt;
      &lt;td&gt;yes&lt;/td&gt;
      &lt;td&gt;Tell people how to get help for the code in the repo&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.github.com/en/communities/setting-up-your-project-for-healthy-contributions/adding-support-resources-to-your-project&quot;&gt;Docs&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;workflow.yml&lt;/td&gt;
      &lt;td&gt;workflow-templates&lt;/td&gt;
      &lt;td&gt;only available in .github repo&lt;/td&gt;
      &lt;td&gt;Store starter workflows for your organizations&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.github.com/en/enterprise-cloud@latest/actions/using-workflows/creating-starter-workflows-for-your-organization&quot;&gt;Starter workflow templates&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;dependabot.yml&lt;/td&gt;
      &lt;td&gt;.github/&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Dependabot configuration file&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates#open-pull-requests-limit&quot;&gt;Dependabot configuration&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;codeql-config.yml&lt;/td&gt;
      &lt;td&gt;.github/codeql/codeql-config.yml (convention, not required)&lt;/td&gt;
      &lt;td&gt;sort of&lt;/td&gt;
      &lt;td&gt;CodeQL configuration file. Can also be stored in an external repository (hence .github repo works). If using external repo, referencing can by done by using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;owner/repository/filename@branch&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.github.com/en/code-security/code-scanning/using-codeql-code-scanning-with-your-existing-ci-system/configuring-codeql-runner-in-your-ci-system#using-a-custom-configuration-file&quot;&gt;CodeQL config&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;secret_scanning.yml&lt;/td&gt;
      &lt;td&gt;.github/secret_scanning.yml&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Secret scanning configuration file&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.github.com/en/code-security/secret-scanning/configuring-secret-scanning-for-your-repositories&quot;&gt;Secret scanning&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;README.md&lt;/td&gt;
      &lt;td&gt;.github, root, or docs directory&lt;/td&gt;
      &lt;td&gt;yes, see below&lt;/td&gt;
      &lt;td&gt;Project readme, also used on marketplace if the repo is published to the marketplace&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-readmes&quot;&gt;About readme’s&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;README.md&lt;/td&gt;
      &lt;td&gt;.github/username/username&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Profile readme&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-readmes&quot;&gt;About readme’s&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;README.md&lt;/td&gt;
      &lt;td&gt;organizations .github &lt;strong&gt;repo&lt;/strong&gt; or .github-private &lt;strong&gt;repo&lt;/strong&gt;: profile/README.md&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Organization readme&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.github.com/en/organizations/collaborating-with-groups-in-organizations/customizing-your-organizations-profile&quot;&gt;Organization readme&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;release.yml&lt;/td&gt;
      &lt;td&gt;.github&lt;/td&gt;
      &lt;td&gt;Automatically generated release notes&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.github.com/en/enterprise-cloud@latest/repositories/releasing-projects-on-github/automatically-generated-release-notes#configuring-automatically-generated-release-notes&quot;&gt;Automatically generated release notes&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;workflow.yml&lt;/td&gt;
      &lt;td&gt;.github/workflows/&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.github.com/en/github/automating-your-workflow/automating-workflows-with-github-actions&quot;&gt;Workflows&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;action.yml/action.yaml&lt;/td&gt;
      &lt;td&gt;root&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Configuration file for an actions repository&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;dependency-review-config.yml&lt;/td&gt;
      &lt;td&gt;.github&lt;/td&gt;
      &lt;td&gt;no&lt;/td&gt;
      &lt;td&gt;Dependency review configuration file&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/actions/dependency-review-action#configuration-options&quot;&gt;Dependency review&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;$GITHUB_STEP_SUMMARY&lt;/td&gt;
      &lt;td&gt;workflow&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Job summary output in markdown&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables&quot;&gt;Job summary&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;github-copilot-files&quot;&gt;GitHub Copilot Files&lt;/h2&gt;
&lt;p&gt;With the rise of AI-powered development tools, GitHub Copilot has introduced its own set of magic files to help customize the AI experience for your specific repository context. These files help Copilot understand your project better and provide more relevant suggestions.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Filename&lt;/th&gt;
      &lt;th&gt;Location&lt;/th&gt;
      &lt;th&gt;.github repo support&lt;/th&gt;
      &lt;th&gt;Description&lt;/th&gt;
      &lt;th&gt;Docs&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;copilot-instructions.md&lt;/td&gt;
      &lt;td&gt;.github/&lt;/td&gt;
      &lt;td&gt;yes&lt;/td&gt;
      &lt;td&gt;Repository-wide custom instructions providing context and coding guidelines to GitHub Copilot for all requests in the repository&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot&quot;&gt;Custom Instructions&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;NAME.instructions.md&lt;/td&gt;
      &lt;td&gt;.github/instructions/&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Path-specific custom instructions that apply to files matching the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;applyTo&lt;/code&gt; glob pattern defined in the file’s YAML frontmatter. Both repository-wide and path-specific instructions are used when both apply&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot&quot;&gt;Custom Instructions&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;AGENTS.md&lt;/td&gt;
      &lt;td&gt;anywhere in the repository&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Agent instructions for Copilot coding agent. The nearest file in the directory tree takes precedence. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLAUDE.md&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GEMINI.md&lt;/code&gt; at the repository root are also supported as alternatives for other AI agents&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot&quot;&gt;Custom Instructions&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;NAME.prompt.md&lt;/td&gt;
      &lt;td&gt;.github/prompts/&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Reusable prompts for specific and repetitive tasks that can be invoked in Copilot Chat. Supports YAML frontmatter for metadata like description and which tools to use&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.github.com/en/copilot/tutorials/customization-library/prompt-files&quot;&gt;Prompt Files&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;NAME.agent.md&lt;/td&gt;
      &lt;td&gt;.github/agents/&lt;/td&gt;
      &lt;td&gt;yes&lt;/td&gt;
      &lt;td&gt;Custom agent profiles with YAML frontmatter defining the agent’s name, description, available tools, and MCP server configurations. Allows creating specialized agents with tailored expertise for specific development tasks. Available on GitHub.com, VS Code, JetBrains, Eclipse, and Xcode&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.github.com/en/copilot/reference/custom-agents-configuration&quot;&gt;Custom Agents&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;settings.json&lt;/td&gt;
      &lt;td&gt;.github-private/.github/copilot/&lt;/td&gt;
      &lt;td&gt;no&lt;/td&gt;
      &lt;td&gt;Enterprise managed plugin standards for GitHub Copilot CLI. Defines a plugin marketplace, auto-installed plugins, MCP server configurations, and hooks that are distributed and applied automatically for all enterprise users with Copilot Business or Copilot Enterprise&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.github.com/copilot/how-tos/administer-copilot/manage-for-enterprise/manage-agents/configure-enterprise-plugin-standards&quot;&gt;Enterprise managed client settings&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Note: content exclusion (preventing Copilot from accessing certain files) is &lt;strong&gt;not&lt;/strong&gt; configured via a file - it is set up through your repository or organization settings on GitHub.com. See the &lt;a href=&quot;https://docs.github.com/en/copilot/how-tos/configure-content-exclusion/exclude-content-from-copilot&quot;&gt;content exclusion docs&lt;/a&gt; for more details.&lt;/p&gt;

&lt;p&gt;These files help you customize the AI experience by:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Providing repository-specific context and coding guidelines through custom instructions&lt;/li&gt;
  &lt;li&gt;Applying specific instructions to certain file types or directories&lt;/li&gt;
  &lt;li&gt;Guiding Copilot’s coding agents with information about your project conventions&lt;/li&gt;
  &lt;li&gt;Creating reusable prompts for common development tasks in your project&lt;/li&gt;
  &lt;li&gt;Building specialized custom agents with their own tools and MCP server configurations&lt;/li&gt;
  &lt;li&gt;Distributing enterprise-wide plugin standards, MCP configs, and hooks to all Copilot CLI users automatically (now in &lt;a href=&quot;https://github.blog/changelog/2026-05-06-enterprise-managed-plugins-in-github-copilot-cli-are-now-in-public-preview/&quot;&gt;public preview&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Just like the other magic files, these need to be named exactly right and placed in the correct directories to work their magic ✨.&lt;/p&gt;

&lt;p&gt;Then there is a whole list of templates you can configure for issues / pull requests / discussion:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Filename&lt;/th&gt;
      &lt;th&gt;Location&lt;/th&gt;
      &lt;th&gt;.github repo support&lt;/th&gt;
      &lt;th&gt;Description&lt;/th&gt;
      &lt;th&gt;Docs&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;FORM-NAME.yml&lt;/td&gt;
      &lt;td&gt;.github/ISSUE_TEMPLATE/&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Issue templates with forms (in Beta for github.com, not available for GHES)&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository&quot;&gt;Templates&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;config.yml&lt;/td&gt;
      &lt;td&gt;.github/ISSUE_TEMPLATE/&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Issue templates configuration settings&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository#configuring-the-template-chooser&quot;&gt;Template chooser&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;issue_template.md&lt;/td&gt;
      &lt;td&gt;.github/ISSUE_TEMPLATE/&lt;/td&gt;
      &lt;td&gt;yes&lt;/td&gt;
      &lt;td&gt;Issue template&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository#configuring-the-template&quot;&gt;Template&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Url query&lt;/td&gt;
      &lt;td&gt;In the url link&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;Create an issue with certain fields filled in with values&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.github.com/en/enterprise-server@3.4/issues/tracking-your-work-with-issues/creating-an-issue#creating-an-issue-from-a-url-query&quot;&gt;Create issue with url query&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;pull_request_template.md&lt;/td&gt;
      &lt;td&gt;root, /docs, /.github or in the PULL_REQUEST_TEMPLATE directory&lt;/td&gt;
      &lt;td&gt;yes&lt;/td&gt;
      &lt;td&gt;Create the default body for a Pull Request&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/creating-a-pull-request-template-for-your-repository&quot;&gt;Using a PR template&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Discussion category templates&lt;/td&gt;
      &lt;td&gt;/.github/DISCUSSION_CATEGORY_TEMPLATES&lt;/td&gt;
      &lt;td&gt;?&lt;/td&gt;
      &lt;td&gt;Create discussion category templates&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.github.com/en/discussions/managing-discussions-for-your-community/creating-discussion-category-forms&quot;&gt;Create discussion category forms&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Some of these are extra tricky, like for example the organization profile lives in a different directory and repo then the user profile readme: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.github&lt;/code&gt; or in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.github-private&lt;/code&gt; repo in the org and then in a folder named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;profile&lt;/code&gt;: README.md.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20211126/20211126-org-profile.jpg.png&quot; alt=&quot;Screenshot of creating the .github repo&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;magic-links&quot;&gt;Magic links&lt;/h2&gt;
&lt;p&gt;There are also some magic links that can be super useful.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Link setup&lt;/th&gt;
      &lt;th&gt;Description&lt;/th&gt;
      &lt;th&gt;Documentation&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;github.com/OWNER/REPO/releases/latest&lt;/td&gt;
      &lt;td&gt;Permalink to the latest release&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.github.com/en/repositories/releasing-projects-on-github/linking-to-releases&quot;&gt;Permalink to latest release&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;github.com/userhandle.keys&lt;/td&gt;
      &lt;td&gt;Get the public part of a users SSH key&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;github.com/userhandle.gpg&lt;/td&gt;
      &lt;td&gt;Get the public part of a users GPG key&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;github.com/userhandle.png&lt;/td&gt;
      &lt;td&gt;Get the profile picture of a user&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;avatars.githubusercontent.com/userhandle?s=32&lt;/td&gt;
      &lt;td&gt;Easy method to show user profile pictures anywhere. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;s&lt;/code&gt; parameter is the size. Example output: &lt;img src=&quot;https://avatars.githubusercontent.com/rajbos?s=32&quot; alt=&quot;Rob&apos;s avatar, which is a face only photo of his dog: Flynn&quot; /&gt;&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;github.com/owner/repo#readme&lt;/td&gt;
      &lt;td&gt;Scroll the repo link to open up with the README text on the page. Since GitHub shows the file content of the repo first, this can be helpful to push you users down the page into the README section. This works because the README is based on a header in the page, so this is just normal HTML behaviour.&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;atom-feeds&quot;&gt;Atom feeds&lt;/h2&gt;
&lt;p&gt;A lot of things have atom feeds enabled. The things in all caps need to be configured:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Link setup&lt;/th&gt;
      &lt;th&gt;Description&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;github.com/OWNER/REPO/commits.atom&lt;/td&gt;
      &lt;td&gt;Get an RSS feed for the commits in that repo&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;github.com/OWNER/REPO/commits/BRANCH.atom&lt;/td&gt;
      &lt;td&gt;Get an RSS feed for all the commits in that branch&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;github.com/OWNER/REPO/wiki.atom&lt;/td&gt;
      &lt;td&gt;Feed for the wiki in that repo&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;github.com/OWNER/REPO/discussions.atom&lt;/td&gt;
      &lt;td&gt;Get an RSS feed for the discussions in that repo&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;github.com/OWNER/REPO/releases.atom&lt;/td&gt;
      &lt;td&gt;Get an RSS feed for the releases in that repo&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;github.com/USER.atom&lt;/td&gt;
      &lt;td&gt;Get an RSS feed for the user’s public activity&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;github.com/security-advisories&lt;/td&gt;
      &lt;td&gt;Get an RSS feed for ALL the security advisories&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;There should also be a feed for issues, but I continuously get HTTP:406 errors on github.com/OWNER/REPO/issues.atom.
Other the user specific feeds can be loaded by making an authenticated call to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://api.github.com/feeds&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can get an entire firehose of ALL issues on the GitHub platform if you want to: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;- https://github.com/issues?q=&lt;/code&gt;.&lt;/p&gt;

&lt;h1 id=&quot;personal-views&quot;&gt;Personal views&lt;/h1&gt;
&lt;ul&gt;
  &lt;li&gt;github.com/issues - get a list of issues that are either created by you, or assigned to you&lt;/li&gt;
  &lt;li&gt;github.com/pulls - get a list of pull requests that are either created by you, or assigned to you&lt;/li&gt;
  &lt;li&gt;github.com/discussions - get a list of discussions that are either created by you, or assigned to you&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;using-separate-git-configurations-with-ssh-for-the-same-user&quot;&gt;Using separate git configurations with SSH for the same user&lt;/h2&gt;
&lt;p&gt;You can add a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-&lt;/code&gt; to the ssh url, to have different ssh configs on you machine and use the right one for the right repo.
Exampe: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git@github.com-myworkaccount:devops-actions/load-used-actions&lt;/code&gt;.
This boils down to using a separate hostname (github.com-myworkaccount) to use for the configured repo (devops-actions/load-used-actions).&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>GitHub Universe session: How to use GitHub Actions with security in mind</title>
			<link href="https://devopsjournal.io/blog/2021/10/27/GitHub-Universe-Session"/>
			<updated>2021-10-27T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2021/10/27/GitHub-Universe-Session</id>
			<content type="html">&lt;p&gt;My session on GitHub Universe is now available &lt;a href=&quot;https://www.youtube.com/watch?v=Ers-LcA7Nmc&amp;amp;list=PLXVVwOM8uv2zyhtF-aHwsyDbqsm_RGOGY&amp;amp;index=5&quot;&gt;here&lt;/a&gt;. You can watch it for free!&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=Ers-LcA7Nmc&amp;amp;list=PLXVVwOM8uv2zyhtF-aHwsyDbqsm_RGOGY&amp;amp;index=5&quot;&gt;&lt;img src=&quot;/images/2021/20211027/20211027_GitHubUniverse.png&quot; alt=&quot;Screenshot of the session on the GitHub Universe website&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;follow-up-questions&quot;&gt;Follow up questions&lt;/h1&gt;
&lt;p&gt;Let me know if there are any follow up questions. You can tag me on the &lt;a href=&quot;https://github.com/githubevents/Universe2021/discussions&quot;&gt;discussions&lt;/a&gt; created for the event by using my GitHub handle: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rajbos&lt;/code&gt; or comment on the YouTube video. I’ll add them below for future reference!&lt;/p&gt;

&lt;h2 id=&quot;setting-up-an-internal-marketplace&quot;&gt;Setting up an internal marketplace&lt;/h2&gt;
&lt;p&gt;A blogpost on the topic to setup an internal marketplace can be found &lt;a href=&quot;https://devopsjournal.io/blog/2021/10/14/GitHub-Actions-Internal-Marketplace&quot;&gt;here&lt;/a&gt;. It contains the full setup from having a process for your users to running security checks on the actions &lt;strong&gt;before&lt;/strong&gt; you fork them over to be used (last past is still a work in progress).&lt;/p&gt;

&lt;h1 id=&quot;slides&quot;&gt;Slides&lt;/h1&gt;
&lt;p&gt;For anyone interested in the slides with all the links I mention, you can find them &lt;a href=&quot;/slides/20211027%20GitHub%20Actions%20security%20GitHub%20Universe.pdf&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Setup an internal GitHub Actions Marketplace</title>
			<link href="https://devopsjournal.io/blog/2021/10/14/GitHub-Actions-Internal-Marketplace"/>
			<updated>2021-10-14T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2021/10/14/GitHub-Actions-Internal-Marketplace</id>
			<content type="html">&lt;p&gt;One of the best practices of using GitHub Actions is to &lt;a href=&quot;/blog/2021/2021/02/06/GitHub-Actions-Forking-Repositories&quot;&gt;fork all actions&lt;/a&gt; that you want to use to your internal actions organization. If often use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;organizationname-actions&lt;/code&gt; for that, just like I am doing for my own personal setup here: &lt;a href=&quot;https://github.com/rajbos-actions&quot;&gt;rajbos-actions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After forking the repositories I always get the question:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;What now?&lt;/li&gt;
  &lt;li&gt;How do we handle internal discovery?&lt;/li&gt;
  &lt;li&gt;How can we have a process that gives our engineers control over the actions that we use?&lt;/li&gt;
  &lt;li&gt;How can we do all this in a secure way?&lt;/li&gt;
  &lt;li&gt;Can we automate this process? How do I stay up to date with the parent repository?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post describes my way of working, and how I set up a GitHub Actions Marketplace.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20211014/20211014_Marketplace.png&quot; alt=&quot;Image of the Actions Marketplace&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The reasons for forking are &lt;a href=&quot;/blog/2021/02/06/GitHub-Actions-Forking-Repositories&quot;&gt;plentiful&lt;/a&gt;, for example:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Take control over the Actions as a backup for your production organization (since they are downloaded just-in-time by the runner)&lt;/li&gt;
  &lt;li&gt;Have a formal moment in your organization that marks the end of a security check on the actions’ source code (very important!)&lt;/li&gt;
  &lt;li&gt;Have a central location for all the actions that can be used inside your production organization (combines nicely with the next item)&lt;/li&gt;
  &lt;li&gt;Block actions from the marketplace from being used in your production organization (see item above)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Want to know more? Check out a previous user group session on it &lt;a href=&quot;/blog/2021/05/28/Solidify-show-Using-GitHub-Actions-Securely&quot;&gt;here&lt;/a&gt; or my 2021 session on &lt;a href=&quot;/blog/2021/10/27/GitHub-Universe-Session&quot;&gt;GitHub Universe 2021&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;After setting the internal marketplace up (see below) that will host the ‘blessed’ actions, we also need to prevent any other actions from being used in our production organization. You have control over this in the organization settings:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20211014/20211014_Limit.png&quot; alt=&quot;Screenshot of actions permissions in GitHub&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;internal-actions-marketplace&quot;&gt;Internal Actions Marketplace&lt;/h1&gt;
&lt;p&gt;The reason for having an internal actions marketplace is to have a central location for all the actions that can be used inside our production organization. This is to prevent any actions from the public marketplace from being used in our production organization, without being checked for security risks first.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20211014/20211014_Organizations.png&quot; alt=&quot;Organization setup example from above&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;guidelines-for-the-actions-organization&quot;&gt;Guidelines for the actions organization&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;DevOps Engineers own the actions when they fork them&lt;/li&gt;
  &lt;li&gt;Before forking actions there is a full security review&lt;/li&gt;
  &lt;li&gt;Requests for adding actions go through an internal repository by adding issues&lt;/li&gt;
  &lt;li&gt;Setup an internal marketplace to discover the internal actions&lt;/li&gt;
  &lt;li&gt;Have a communication plan on new actions, for example by having an inner source sharing platform (newsletter?)&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;full-marketplace-process&quot;&gt;Full marketplace process&lt;/h1&gt;
&lt;p&gt;The process for adding actions to the marketplace is as follows:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;User finds an action on the marketplace&lt;/li&gt;
  &lt;li&gt;Request through an issue to include it&lt;/li&gt;
  &lt;li&gt;Manual security validation&lt;/li&gt;
  &lt;li&gt;Issue gets labeled &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;security-check&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Security validation on the issue (scan the source repo)&lt;/li&gt;
  &lt;li&gt;Decide on the risk of forking the action and using it&lt;/li&gt;
  &lt;li&gt;Sign off by security team [optional, can be handled by the next step]&lt;/li&gt;
  &lt;li&gt;Issue gets labeled &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;security-approved&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Fork it and own the maintenance&lt;/li&gt;
  &lt;li&gt;Share fixes back to the parent repo&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;request-through-an-issue-to-include-it&quot;&gt;Request through an issue to include it&lt;/h2&gt;
&lt;p&gt;I’ve setup a (start of a) example project &lt;a href=&quot;https://github.com/rajbos/github-actions-requests&quot;&gt;github-actions-requests&lt;/a&gt; that is used to request actions to be added to the internal marketplace.&lt;/p&gt;

&lt;p&gt;To do so:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;a user needs to create an issue in a specifically setup repository, describing the action and the reason for requesting it. It helps if they already have an example repo for which they would like to use it.&lt;/li&gt;
  &lt;li&gt;a DevOps engineer with a security mindset / background checks out the actions repository and reviews the code (see more below)&lt;/li&gt;
  &lt;li&gt;after the initial and manual check, the engineer labels the issue with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;security-check&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;an automated security check is done on the actions repository, with communication in the issue (security scores for example)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;manual-security-validation&quot;&gt;Manual security validation&lt;/h2&gt;
&lt;p&gt;A DevOps engineer can check out the actions repository and review the code. This is done by:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;looking at the setup of the action: is it JavaScript, Typescript or using a Docker container?&lt;/li&gt;
  &lt;li&gt;is the action only doing what it is meant to do?&lt;/li&gt;
  &lt;li&gt;does the action read files from disk outside of the work folder? (e.g. your ssh keys)&lt;/li&gt;
  &lt;li&gt;does the action read any environment variables?&lt;/li&gt;
  &lt;li&gt;what does it do with all the information it has access to? (e.g. does it sent is out to an external endpoint?)&lt;/li&gt;
  &lt;li&gt;does it use the &lt;a href=&quot;https://docs.github.com/en/actions/security-guides/automatic-token-authentication&quot;&gt;GitHub Token&lt;/a&gt; for anything? If so, is it safe?&lt;/li&gt;
  &lt;li&gt;does it support GitHub Enterprise Server (GHES) if you need it? (e.g. does it use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github.com&lt;/code&gt; domain for anything, or does it use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GITHUB_API_URL&lt;/code&gt; environment variable?)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;security-validation&quot;&gt;Security validation&lt;/h2&gt;
&lt;p&gt;For the security validation you can use your own internal setup. You need something that runs a &lt;a href=&quot;https://wiki.owasp.org/images/b/bd/Software_Composition_Analysis_OWASP_Stammtisch_-_Stanislav_Sivak.pdf&quot;&gt;Software Composition Analysis (SCA)&lt;/a&gt; scan and from that get security alerts for any dependencies that have them.&lt;/p&gt;

&lt;p&gt;There are lots of tools available for this, for example &lt;a href=&quot;https://www.synopsys.com/software-integrity/security-testing/software-composition-analysis.html&quot;&gt;Black Duck&lt;/a&gt; or &lt;a href=&quot;https://www.whitesourcesoftware.com/resources/blog/software-composition-analysis/&quot;&gt;White Source&lt;/a&gt;. GitHub already has &lt;a href=&quot;https://github.blog/2020/2020-06-01-keep-all-your-packages-up-to-date-with-dependabot/#stop-using-vulnerable-dependencies-dependabot-alerts-and-security-updates&quot;&gt;Dependabot&lt;/a&gt; available that can be used for free on public repos. Since a fork is already a public repo, you can use it to scan the actions source code as well.&lt;/p&gt;

&lt;p&gt;For our final setup I want to have it automated as much as we can, so I’ll be describing that process here. After the initial validation is completed and satisfies internal requirements, I want to label the repository with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;security-validation&lt;/code&gt; and run automated security validation on it. More on that below, but I am setting that up as well in the &lt;a href=&quot;https://github.com/rajbos/github-actions-requests&quot;&gt;example repo here&lt;/a&gt;.
The results from the checks will be added to the issue as badges from the different systems, with deep links into those systems to check the analysis. That can then be used for the final checks.&lt;/p&gt;

&lt;h3 id=&quot;dependabot&quot;&gt;Dependabot&lt;/h3&gt;
&lt;p&gt;For Software Composition Analysis, we can use Dependabot to scan the actions source code for the packages that it uses. You can see the results of one of my actions in the repositories &lt;a href=&quot;https://github.com/rajbos/github-action-load-available-actions/network/dependencies&quot;&gt;Dependency Graph&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20211014/20211014_DependencyGraph.png&quot; alt=&quot;Screenshot of the dependency graph&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Here you find the direct dependencies (in this case from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;packages.json&lt;/code&gt; file of the repository) and all the ‘transient’ dependencies. A transient dependency is a dependency used by one of your direct dependencies. And since the transient dependency can have its own dependencies… it can be a long list. This is why some research shows that 70% of the code you deploy, was never created by you, but pulled in through a dependency. This is of course why a Software Composition Analysis (SCA) is so important to know about the dependencies that you have and match them to a known vulnerability database, also called a ‘Common Vulnerabilities and Exposures’ or ‘CVE’. Some examples of these databases are the &lt;a href=&quot;https://nvd.nist.gov/&quot;&gt;National Vulnerability Database from NIST&lt;/a&gt; or the &lt;a href=&quot;https://cve.mitre.org/&quot;&gt;CVE database from Mitre&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;GitHub has its own &lt;a href=&quot;https://github.com/advisories&quot;&gt;GitHub Advisory Database&lt;/a&gt; as well, with lots of vulnerabilities listed in it:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20211014/20211014_GH_Advisories.png&quot; alt=&quot;Screenshot of GitHub Advisory Database&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;security-advisories&quot;&gt;Security Advisories&lt;/h3&gt;
&lt;p&gt;After Dependabot has scanned the actions source code, it knows which dependencies are being used. Next, it will generate a list of security advisories for the packages that it found. It can even generate pull requests to the repository to update the vulnerable packages to a non-vulnerable version. Since we’re using it here on a public fork, we don’t use it ourselves. The action publisher should use it on their end to fix the issues found. You could of course use it on your fork and then send a Pull Request to the parent repo to fix the issues, and let the publisher know that they can use these features as well.&lt;/p&gt;

&lt;p&gt;In this setup we can use the findings from the Dependabot scan to validate if we can use the action without large vulnerabilities in the packages used.&lt;/p&gt;

&lt;h3 id=&quot;codeql&quot;&gt;CodeQL&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://codeql.github.com/&quot;&gt;CodeQL&lt;/a&gt; is a tool that can be used to scan the actions source code for vulnerabilities and available for free on public repositories, which our forks are. You configure it as a workflow like I’ve done &lt;a href=&quot;https://github.com/rajbos/github-action-load-available-actions/blob/main/.github/workflows/codeql-analysis.yml&quot;&gt;here&lt;/a&gt;. It will use action minutes for its execution.&lt;/p&gt;

&lt;p&gt;Be aware that it by default scans the entire repository. In my case, I have a Typescript based action. That means that the Typescript is transpiled to JavaScript and then uploaded to the repository. So CodeQL will then scan everything. This meant that I found an &lt;a href=&quot;https://github.com/moment/moment/issues/5946&quot;&gt;issue&lt;/a&gt; in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;moment.js&lt;/code&gt; through the JavaScript in the repository. Only checking the Typescript code doesn’t find that for example of course, since it is not in that part of the code.&lt;/p&gt;

&lt;p&gt;The CodeQL workflow will upload its scan results to GitHub, that can be found by going to ‘Security’ and then ‘Code scanning alerts’:
&lt;img src=&quot;/images/2021/20211014/20211014_SecurityAlert.png&quot; alt=&quot;Screenshot of the scanning alert on the JavaScript code&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You can then review the vulnerability listed and check the recommendation.&lt;/p&gt;
&lt;h5 id=&quot;note-finding-the-source-of-the-code-vulnerability-in-the-javascript-code-can-be-very-hard-transpile-your-typescript-with-the-inlinesourcemap-setting-to-make-it-easier-to-find-the-actual-typescript--dependency-source&quot;&gt;Note: finding the source of the code vulnerability in the JavaScript code can be very hard. Transpile your Typescript with the &lt;a href=&quot;https://www.typescriptlang.org/tsconfig#inlineSourceMap&quot;&gt;inlineSourceMap&lt;/a&gt; setting to make it easier to find the actual Typescript / dependency source.&lt;/h5&gt;

&lt;h3 id=&quot;container-scanning&quot;&gt;Container scanning&lt;/h3&gt;
&lt;p&gt;Since the Actions can also be run as a container, we need to check those dependencies as well. That means scanning the container from the image setting in the &lt;a href=&quot;https://docs.github.com/en/actions/creating-actions/creating-a-docker-container-action#creating-an-action-metadata-file&quot;&gt;action.yaml&lt;/a&gt;. This setting can also refer to a Dockerfile in the root of the repository:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# action.yml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Hello&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;World&apos;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Greet&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;someone&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;record&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;the&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;time&apos;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;inputs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;who-to-greet&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# id of input&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Who&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;greet&apos;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;required&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;World&apos;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;outputs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# id of output&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;The&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;we&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;greeted&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;you&apos;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;runs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;using&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;docker&apos;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Dockerfile&apos;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;$&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Scanning the containers can be done by using something like &lt;a href=&quot;https://github.com/aquasecurity/trivy&quot;&gt;Trivy&lt;/a&gt; or &lt;a href=&quot;https://github.com/anchore/anchore-engine&quot;&gt;Anchore&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;security-approved&quot;&gt;Security approved&lt;/h2&gt;
&lt;p&gt;When you have done all the security checks, we can do a formal approval of the action and on board it to our own actions organization on GitHub. Labeling the issue as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;security-approved&lt;/code&gt; will trigger a workflow that:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Forks the repository to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;organization-actions&lt;/code&gt; organization&lt;/li&gt;
  &lt;li&gt;Updates the issue with that information&lt;/li&gt;
  &lt;li&gt;Closes the issue since the request has been fulfilled&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;fork-it-and-own-the-maintenance&quot;&gt;Fork it and own the maintenance&lt;/h2&gt;
&lt;p&gt;Now that we have forked the action, it’s up to us to maintain it, update it with the latest changes from the parent repo and fix any issues that might arise (and send those back to the parent repo!). Keeping everything up to date with incoming changes from the parent repo is something that I blogged about earlier &lt;a href=&quot;/blog/2021/02/06/GitHub-Actions-Forking-Repositories&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h5 id=&quot;note-we-also-need-a-process-to-handle-new-code-scanning-alerts-on-the-repository-and-a-way-to-keep-the-codeql-workflow-running-since-it-is-automatically-stopped-after-90-days-of-no-new-code-changes-in-the-repository-then-we-also-need-to-handle-any-new-security-alerts-from-codeql-as-well&quot;&gt;Note: We also need a process to handle new code scanning alerts on the repository, and a way to keep the CodeQL workflow running, since it is automatically stopped after 90 days of no new code changes in the repository. Then we also need to handle any new security alerts from CodeQL as well.&lt;/h5&gt;

&lt;h1 id=&quot;internal-marketplace&quot;&gt;Internal marketplace&lt;/h1&gt;
&lt;p&gt;Now that we have all that setup, we need to have a good way to discover the actions that are available within our actions organization. We can use the default repo overview page, but that doesn’t feel very user friendly. We want a searchable list of actions, with more information then the default repository description. Since there is nothing available out of the box, I created something myself.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20211014/20211014_Marketplace.png&quot; alt=&quot;Screen shot of internal marketplace at https://rajbos-actions.github.io/actions-marketplace/&quot; /&gt;&lt;/p&gt;

&lt;p&gt;With the setup from &lt;a href=&quot;https://github.com/rajbos/actions-marketplace&quot;&gt;actions-marketplace&lt;/a&gt; I’ve created an actions marketplace out of the box: you can fork it, configure it and use &lt;a href=&quot;https://guides.github.com/features/pages/&quot;&gt;GitHub Pages&lt;/a&gt; to host your website. With it your internal users have a central place to search for internal actions. I also want to include links in it to the internal workflows that use the actions, so you can find examples easily. For the Marketplace maintainers, this will also give them a way to track the actions internal usage: if the action is no longer used in any workflow, you might want to remove it from the Marketplace and save you some maintenance work.&lt;/p&gt;

&lt;h2 id=&quot;setup-the-marketplace&quot;&gt;Setup the Marketplace&lt;/h2&gt;
&lt;p&gt;The marketplace repo has been setup with three main components:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Gathering the available actions repositories in an organization&lt;/li&gt;
  &lt;li&gt;Gather the &lt;strong&gt;used&lt;/strong&gt; actions from an organization&lt;/li&gt;
  &lt;li&gt;Host the marketplace with GitHub Actions to display the info from the previous steps&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;gathering-the-available-actions-repositories-in-an-organization&quot;&gt;Gathering the available actions repositories in an organization&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/rajbos/actions-marketplace/blob/main/.github/workflows/get-action-data.yml&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;get-action-data.yml&lt;/code&gt;&lt;/a&gt; workflow loads all the repositories from an organization it has access to, checks the root directory for an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;action.yml&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;action.yaml&lt;/code&gt; file and parses it for information. The result will be a json file stored in the target repository with a specific branch named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gh-pages&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;gather-the-used-actions-from-an-organization&quot;&gt;Gather the used actions from an organization&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/rajbos/actions-marketplace/blob/main/.github/workflows/get-action-usages.yml&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;get-action-usages.yml&lt;/code&gt;&lt;/a&gt; workflow loads all the repositories from an organization (can be a different one then the other step) it has access to, checks the workflows directory for all files with a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.yml&lt;/code&gt; extension and parses it for information. The result will be a json file stored in the target repository with a specific branch named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gh-pages&lt;/code&gt;.&lt;/p&gt;

&lt;h1 id=&quot;summary&quot;&gt;Summary&lt;/h1&gt;
&lt;p&gt;In this post I’ve given you a way to get started with an Internal Marketplace for GitHub Actions, to take back responsibility for the usage and maintenance of the actions. I also shown how to incorporate some security checks and some examples of setting all this up.&lt;/p&gt;

&lt;p&gt;Got any feedback or more questions on how to set things up? Please let me know!&lt;/p&gt;

&lt;h6 id=&quot;note-the-github-actions-requests-example-repository-that-i-am-setting-up-currently-only-does-the-first-few-steps-im-still-building-the-rest-of-the-workflow-&quot;&gt;Note: the &lt;a href=&quot;https://github.com/rajbos/github-actions-requests&quot;&gt;github-actions-requests&lt;/a&gt; example repository that I am setting up currently only does the first few steps. I’m still building the rest of the workflow 😄.&lt;/h6&gt;

&lt;h2 id=&quot;current-status&quot;&gt;Current status:&lt;/h2&gt;
&lt;p&gt;Currently the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github-actions-request&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;is triggered by labeling the issue&lt;/li&gt;
  &lt;li&gt;searches for a uses statement in the last comment of the issue&lt;/li&gt;
  &lt;li&gt;if found, it forks the repository to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;organization-actions&lt;/code&gt; organization (hardcoded in the workflow at the moment)&lt;/li&gt;
  &lt;li&gt;adds a CodeQL workflow file to the forked repository&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since the ‘organization-actions’ organization has been setup to enable the Dependency Graph and Dependabot alerts, I don’t need to do that in the workflow. I just need a way (after a certain amount of time) to check if there are any alerts and included that back into the issue information.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>GitHub Actions: Convert from PowerShell to Typescript</title>
			<link href="https://devopsjournal.io/blog/2021/09/12/GitHub-Actions-conversion-from-powershell-to-typescript"/>
			<updated>2021-09-12T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2021/09/12/GitHub-Actions-conversion-from-powershell-to-typescript</id>
			<content type="html">&lt;p&gt;I am an avid PowerShell user and have been using it for a while now. Together with C# it is my main development experience these days. That is why I created my first GitHub Actions in PowerShell. Using PowerShell in you actions is possible by running the scripts in a container with PowerShell installed.&lt;/p&gt;

&lt;p&gt;I wanted to create the same workflow in Typescript but am not that versed in the language. Together with finding the running in a container somewhat cumbersome (it takes time to pull the image and testing locally needs an extra script). I also found running in a container poses an extra step to use it in a GitHub Enterprise Environment (you probably need to mirror the image internally).&lt;/p&gt;

&lt;p&gt;With an Xpirit Innovation Day coming up I had all the reasons I needed (and time available 😅) to create a Typescript version of the same workflow. I started the &lt;a href=&quot;https://www.hanselman.com/blog/yak-shaving-defined-ill-get-that-done-as-soon-as-i-shave-this-yak&quot;&gt;yak shaving&lt;/a&gt; earlier and made sure I had the basics under control so I would not have to find any of that out during the innovation day itself:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;have a running example of a typical GitHub Action&lt;/li&gt;
  &lt;li&gt;with a way to test it in my IDE (VsCode)&lt;/li&gt;
  &lt;li&gt;with a way to read inputs and a way to inject them for testing in the IDE&lt;/li&gt;
  &lt;li&gt;have logging under control, so I can see what is happening while running&lt;/li&gt;
  &lt;li&gt;have a way to output the results so they can be used in the next action in a workflow&lt;/li&gt;
  &lt;li&gt;know how Typescript pass stuff like arrays back and forth between methods and how to loop over them&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post will be about my learnings moving from PowerShell to Typescript.&lt;/p&gt;

&lt;h1 id=&quot;getting-started&quot;&gt;Getting started&lt;/h1&gt;
&lt;p&gt;To get started I recommend looking at the example repository &lt;a href=&quot;https://github.com/actions/typescript-action&quot;&gt;actions/typescript-action&lt;/a&gt;. It has been setup as a template, which means you can click on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use this template&lt;/code&gt; button and you are good to go.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210912_1/20210912_Template.png&quot; alt=&quot;Screenshot of the template repository&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;learnings-typescript&quot;&gt;Learnings: Typescript&lt;/h1&gt;
&lt;p&gt;Converting to Typescript definitely took some time for me. The language is locked down regarding types and usages of things you declare:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;If you declare a variable and don’t use it: you’ll get a compiler error.&lt;/li&gt;
  &lt;li&gt;If it cannot figure out what type an object is, you’ll get a compiler error.&lt;/li&gt;
  &lt;li&gt;If a variable could be null or undefined, you’ll get a compiler error using it. When you add a null check first, the compiler error goes away.
All in all it reminds me much of C# 8’s nullable reference types: you are forced to make sure you are handing the nullable objects correctly which is nice, but can add a bit of a burden when you are new to the language. I also found out my head already declares things that I’ll need later on and then the IDE starts complaining that it isn’t used yet. It also seems to stop compiling further ahead of the error, which I don’t like.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The template also has ESLint configured, which I think is a good thing. It is a linter that checks for some common mistakes and helps you to fix them. It also has a code style checker that helps you to fix the code style. The style checker feels a bit enforced and gets in the way of typing: you cannot see if you have a compiler error at hand or that it is a linting violation 😢.&lt;/p&gt;

&lt;h2 id=&quot;typescript-needs-compilation-to-run&quot;&gt;Typescript needs compilation to run&lt;/h2&gt;
&lt;p&gt;GitHub Actions in the end runs a NodeJS script. To enable Typescript to run, you need to compile the script to a JavaScript file for things to work. For this you can use &lt;a href=&quot;https://github.com/vercel/ncc&quot;&gt;@vercel/ncc&lt;/a&gt; to compile the script into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dist&lt;/code&gt; folder. Then you need to add the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dist&lt;/code&gt; folder to the repository and push it to GitHub for things to work. Adding compiled code to the repository off course goes against my DevOps heart, but this is how it works for Actions: it could directly be checked out by adding a commit SHA or the branch name to your uses statement, as you &lt;a href=&quot;/blog/2021/02/06/GitHub-Actions-Forking-Repositories&quot;&gt;should&lt;/a&gt; and then it is expected to still work.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210912_1/20210912_Dist.png&quot; alt=&quot;Screenshot of the dist folder with index.js, index.js.map, licenses.txt and a sourcemap file in it&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;octokit&quot;&gt;Octokit&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://octokit.github.io/&quot;&gt;Octokit&lt;/a&gt; library is very useful to make the GitHub API calls. It wraps the GitHub API by leveraging the official &lt;a href=&quot;https://github.com/github/rest-api-description&quot;&gt;REST API Description&lt;/a&gt; for GitHub. For example connecting to the API with authentication is very straight forward:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210912_1/20210912_OctokitAuthenticated.png&quot; alt=&quot;Code showing new Octokit with a PAT to make an authenticated call to the rest API&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In PowerShell I had to wrap all calls to use my own authentication setup and convert the PAT to a Base64 encoded header. It’s working, but Octokit saves me all that trouble 😄.
Similarly handling API rate-limiting is handled for you, so you don’t have to worry about it.&lt;/p&gt;

&lt;h2 id=&quot;octokit-and-inputs&quot;&gt;Octokit and inputs&lt;/h2&gt;
&lt;p&gt;You can declare inputs in your action that the workflow can inject as parameters for your action. Getting the values of the parameters at runtime is build into Octokit, but injecting them during debugging is not. You can use the environment variables from the NodeJS process to still load them.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210912_1/20210912_Inputs.png&quot; alt=&quot;Image of using process.env.PAT on top of core.getInput&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;octokit-and-pagination&quot;&gt;Octokit and pagination&lt;/h2&gt;
&lt;p&gt;I already have more than 30 repositories in my user account, which means I have to make calls to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getRepos&lt;/code&gt; already implementing pagination to get all the results. In PowerShell this meant wrapping my calls, check for the pagination headers and handling them properly. Then I needed to stitch all the results into one list and return that to the caller.&lt;/p&gt;

&lt;p&gt;In Octokit this is done by added &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;octokit.paginate()&lt;/code&gt; around your call and you are done!
&lt;img src=&quot;/images/2021/20210912_1/20210912_Pagination.png&quot; alt=&quot;example of wrapping your call&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;octokit-and-debugginglogging&quot;&gt;Octokit and debugging/logging&lt;/h2&gt;
&lt;p&gt;During debugging locally, I would like to see the messages being logged for the user during the execution, to get an idea of the context we’re working in. The library has concepts for logging with different levels:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;core.info: for general information&lt;/li&gt;
  &lt;li&gt;core.warning: for warnings&lt;/li&gt;
  &lt;li&gt;core.setFailed: for logging an error and stopping execution
The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;core&lt;/code&gt; calls work nicely during the execution of a workflow, but they don’t show up in the debug console! That is not helpful. The only thing that you can do to still get output in your debug console is to fall back to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;console.log&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;summary&quot;&gt;Summary&lt;/h1&gt;
&lt;p&gt;Overall the experience was smooth: the main issues I had was with using Typescript instead of PowerShell. Luckily adopting a new programming language is made easier if you have a good grasp of the core concepts, so you can search for how to do a for loop in a new language, or using Promises, immutable data structures, etc.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/actions/typescript-action&quot;&gt;actions/typescript-action&lt;/a&gt; template is very complete and can be used out of the box.
I also noticed the execution of the action is much faster when using Typescript. Of course, the container doesn’t have to be pulled, but also all my handling in PowerShell with authentication, pagination and checking if we’re not hitting the rate limit is much faster.&lt;/p&gt;

&lt;p&gt;Iterating through 36 repos (at the time), checking all repos for an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;action.yml&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;action.yaml&lt;/code&gt; file, parsing the yaml and storing the information in an array when from 35 seconds (PowerShell) to 6 seconds (Typescript).&lt;/p&gt;

&lt;p&gt;Using the Typescript template also had unit tests installed, which I could do in PowerShell as well, but never did. Let’s see if I’ll use them now 😄. Still, the ultimate test is including a workflow in your repository that will run your local action and check if it works.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210912_1/20210912_Testing.png&quot; alt=&quot;Image of &apos;uses ./&apos; to run the local action in a workflow&quot; /&gt;&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>GitHub Actions: Run PowerShell in Container</title>
			<link href="https://devopsjournal.io/blog/2021/09/12/GitHub-Actions-container-with-powershell"/>
			<updated>2021-09-12T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2021/09/12/GitHub-Actions-container-with-powershell</id>
			<content type="html">&lt;p&gt;You can create GitHub Actions running in a container, which allows you to execute ‘anything’ in an action that can be run inside a container, including PowerShell, my favorite scripting language. To get started, you can use the available &lt;a href=&quot;https://github.com/actions/container-action&quot;&gt;template repo&lt;/a&gt; to create a new repository filled with the contents of the template.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210912/20210912_Template.png&quot; alt=&quot;Image of the template repository with a border around the &apos;use this template&apos; button&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The moving parts of the template are as follows:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;action.yml&lt;/li&gt;
  &lt;li&gt;Dockerfile&lt;/li&gt;
  &lt;li&gt;entrypoint.sh&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;actionyml&quot;&gt;action.yml&lt;/h2&gt;
&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;action.yml&lt;/code&gt; contains the setup for your action repository when it is used in a GitHub Actions workflow as well as the listing of it on the GitHub Actions Marketplace. In it are the description of the action, the author, the version, and the list of inputs and outputs.&lt;/p&gt;

&lt;p&gt;It also tells the workflow runner (the thing that executes the action), how to run the action. In this case we use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;using&lt;/code&gt; property to tell the engine to use a Docker image to run in. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;image&lt;/code&gt; property tells the engine which Docker image to use. If you use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dockerfile&lt;/code&gt; as its value, it will use the Docker file in the root of the current repository. In the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;args&lt;/code&gt; section you tell it the arguments to pass into the container when it starts. These are then added &lt;strong&gt;in the order the are added to this list&lt;/strong&gt;. So the array has index 0 for the first argument, 1 for the second, and so on. That will also be the order in which the arguments are passed to the entrypoint file in the container.
&lt;img src=&quot;/images/2021/20210912/20210912_ActionYML.png&quot; alt=&quot;image of the args being passed in as an array: $ and $&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;dockerfile&quot;&gt;Dockerfile&lt;/h2&gt;
&lt;p&gt;The ‘Dockerfile’ will be build on the spot when the action is executed. With the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ENTRYPOINT&lt;/code&gt; you tell it what script to run when the container starts. This is the point where you can put your PowerShell script. Remember that the parameters are injected into the script as variables.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-Docker&quot;&gt;FROM ghcr.io/rajbos/actions-marketplace/powershell:7

COPY /Src/PowerShell/*.ps1 /src/

ENTRYPOINT [&quot;pwsh&quot;, &quot;/src/entrypoint.ps1&quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;powershell&quot;&gt;PowerShell&lt;/h2&gt;
&lt;p&gt;The PowerShell script is in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;entrypoint.ps1&lt;/code&gt; file. I declare the parameters that PowerShell will then map in the order the script was started with. Remember that the order is determined in the action.yml file. Which is nice, since that means you are in control of the order and the user cannot make mistakes with it. If the arguments are optional, &lt;em&gt;they will still be injected but with the default or empty value for them&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;You can leverage that feature like I do below. I test for the values to see if they are empty and if so, I use a default environment variable to run in the current GitHub context (a user account or an organization). If not all minimal values have been set, I still fail the action.&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kr&quot;&gt;param&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$organization&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$PAT&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;$null&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-eq&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$organization&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-or&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-eq&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$organization&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Using default for organization: [&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;GITHUB_REPOSITORY_OWNER&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;]&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$organization&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;GITHUB_REPOSITORY_OWNER&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;$null&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-eq&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$PAT&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-or&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-eq&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$PAT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Error&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;No value given for input PAT: Use at least [GITHUB_TOKEN]&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;throw&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$actions&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;\load-used-actions.ps1&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-orgName&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$organization&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-PAT&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$PAT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note: failing the action can be done by exiting the entrypoint script with a non-zero exit code.
I use this setup for it:&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kr&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# always run in the correct location, where our scripts are located:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Set-Location&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;$PSScriptRoot&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

    &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# call main function:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

    &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# return the container with the exit code = Ok:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;exit&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# return the container with an erroneous exit code:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Error&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Error loading the actions:&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Error&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;$_&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# return the container with an erroneous exit code:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;exit&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;powershell-container-image&quot;&gt;PowerShell container image&lt;/h1&gt;
&lt;p&gt;For full reference you can find the Dockerfile I used below:&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;FROM ubuntu:20.04

&lt;span class=&quot;c&quot;&gt;# install powershell for Ubuntu 20.04&lt;/span&gt;
RUN apt-get update &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; wget apt-transport-https software-properties-common &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; wget &lt;span class=&quot;nt&quot;&gt;-q&lt;/span&gt; https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get update &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# Register the Microsoft repository GPG keys&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; dpkg &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; packages-microsoft-prod.deb &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# Update the list of products&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; add-apt-repository universe &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get update &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;libssl-dev &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;gss-ntlmssp &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;powershell &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;git &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# install the module we need&lt;/span&gt;
RUN &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;pwsh&quot;&lt;/span&gt;, &lt;span class=&quot;s2&quot;&gt;&quot;-Command&quot;&lt;/span&gt;, &lt;span class=&quot;s2&quot;&gt;&quot;install-module -name powershell-yaml -Force -Repository PSGallery&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
SHELL &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;pwsh&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
		</entry>
	
		<entry>
			<title>Hosting GitHub runners on Kubernetes</title>
			<link href="https://devopsjournal.io/blog/2021/08/06/GitHub-runners-on-kubernetes"/>
			<updated>2021-08-06T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2021/08/06/GitHub-runners-on-kubernetes</id>
			<content type="html">&lt;p&gt;If you need to host your own &lt;a href=&quot;https://github.com/features/actions&quot;&gt;GitHub Actions&lt;/a&gt; runners that will execute your workflow, you have multiple options: you can go the traditional route and host them on your own VM’s. You can even host multiple runners on the same VM, if you want to increase the density and reuse your CPU or RAM capacity on your VM. The first downside of this is that VM’s are harder to scale and often a bit more costly to run then other options. Even more important reason not to use VM’s because it is not a great option for having an environment that is clean for every run: GitHub Actions will leave several files on disk for both reuse (the actions downloaded and used for example, or the docker images you run on). Even the &lt;a href=&quot;https://github.com/actions/checkout&quot;&gt;checkout&lt;/a&gt; action will only cleanup the source code when it is executed, to make sure that the latest changes are checked out. You can include a cleanup action at the end, but often that is not added.&lt;/p&gt;

&lt;p&gt;Even worse are the &lt;a href=&quot;/blog/2021/2021/03/07/GitHub-Actions-one-workflow-per-runner&quot;&gt;potential security pitfalls&lt;/a&gt; that come from reusing an environment between runs of a workflow, or different workflows in different repositories: the first run could leave some files behind, like from a package manager you use, or overwrite a local docker image for example. Subsequent runs on that machine will look in the local cache first, and use the (potentially) compromised files. These are some of the examples of supply chain attacks that are &lt;a href=&quot;https://xpir.it/Solorigate&quot;&gt;more and more&lt;/a&gt; common these days.&lt;/p&gt;

&lt;p&gt;To combat those risks, you want to have ephemeral runners: the environment only exists during the execution of the workflow: after it is done, everything is cleaned up. The does mean that caching things becomes a little harder. There are ways to combat that with for example a proxy close by your environment that can do the caching for your (note: this is still a potential risk!).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210806/ian-dooley-DuBNA1QMpPA-unsplash.jpg&quot; alt=&quot;Photo of air balloons against a blue sky&quot; /&gt;&lt;/p&gt;
&lt;h6 id=&quot;photo-by-ian-dooley-on-unsplash&quot;&gt;Photo by &lt;a href=&quot;https://unsplash.com/@sadswim?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;ian dooley&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/s/photos/launch?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/h6&gt;

&lt;h1 id=&quot;ephemeral-runners&quot;&gt;Ephemeral runners&lt;/h1&gt;
&lt;p&gt;GitHub does not have any support for hosting your runner inside a container with some ‘bring your own compute’ options. It uses that setup on the GitHub hosted runners, where a runner environment is created just for your run and destroyed afterwards, but hasn’t released anything for their customers. When you start looking for options, you will find a community curated lists of &lt;a href=&quot;https://github.com/jonico/awesome-runners&quot;&gt;awesome runners&lt;/a&gt; that have been made by the community to host your runners inside k8s, AWS EC2, AWS Lambda’s, Docker, GKE, OpenShift or Azure VM’s (at the time of writing 😄).&lt;/p&gt;

&lt;h1 id=&quot;actions-runner-controller&quot;&gt;Actions runner controller&lt;/h1&gt;
&lt;p&gt;The one that got recommended to me was the &lt;a href=&quot;https://github.com/actions-runner-controller/actions-runner-controller&quot;&gt;actions-runner-controller&lt;/a&gt;: it had an active community (82 contributors, including me now! lots of stars) with a lot of communication on the issues and discussions list.&lt;/p&gt;

&lt;h1 id=&quot;hosting-in-azure-kubernetes-service&quot;&gt;Hosting in Azure Kubernetes Service&lt;/h1&gt;
&lt;p&gt;For testing to see if I could get things to working with my bare minimum of k8s knowledge, I created an &lt;a href=&quot;https://azure.microsoft.com/en-us/services/kubernetes-service?WT.mc_id=AZ-MVP-5003719&quot;&gt;Azure Kubernetes Service&lt;/a&gt; cluster with all the defaults and installed the actions runner controller in it, with all the information in the &lt;a href=&quot;(https://github.com/actions-runner-controller/actions-runner-controller)&quot;&gt;readme&lt;/a&gt; of the project. Even used a GitHub app for authentication and things worked straight out of the box. You can just run one runner for quick testing, or use the build in scaling options to scale up and down between your limits (for example 0 runners when there is nothing to run and 100 runners as a maximum), or scale up and down based on time windows you define (so scale up to 30 runners at 7AM on workdays, if your company still folows tradditional time slots people are working on).&lt;/p&gt;

&lt;h2 id=&quot;a-remark-on-scaling&quot;&gt;A remark on scaling&lt;/h2&gt;
&lt;p&gt;During testing I found that the scaling options for &lt;a href=&quot;https://github.com/actions-runner-controller/actions-runner-controller&quot;&gt;actions-runner-controller&lt;/a&gt; have a downside: it can only look at the current queue of &lt;strong&gt;workflows&lt;/strong&gt; and not the set of &lt;strong&gt;jobs&lt;/strong&gt; inside those workflows. That is because GitHub currently does not support loading the queue based on jobs inside workflows. There is development being done on that side, but I have not seen any progress yet.&lt;/p&gt;

&lt;h1 id=&quot;hosting-on-internal-rancher-server&quot;&gt;Hosting on internal Rancher server&lt;/h1&gt;
&lt;p&gt;My customer that wanted this setup for their own GitHub Enterprise Server (GHE) to have a local runner as well. The security team also wanted to do a security check on the setup and didn’t want to use AKS for that (and notify Microsoft of active pen testing activities on the cluster). They had an internal &lt;a href=&quot;https://rancher.com/&quot;&gt;Rancher&lt;/a&gt; setup available that they wanted me to use. The thing is, that this cluster was already tight down a lot: it only could pull internal Docker images, and it had a lot of other restrictions, like using a proxy for all its traffic. This is where things got a little bit more complicated. Their internal images host was a private registry hosted on Artifactory and pulling from public container registries was not available.&lt;/p&gt;

&lt;h1 id=&quot;configuring-images-used-by-the-controller-manager&quot;&gt;Configuring images used by the controller manager&lt;/h1&gt;
&lt;p&gt;The first thing I ran into was that our Rancher setup sat behind an internal proxy / load balancer that forced all images to be downloaded from an internal Artifactory registry. Depending on the original registry a lookup was done in the allow listed images in Artifactory. These where the images we added to the allow list in Artifactory:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;summerwind/actions-runner-controller:v0.18.2&lt;/li&gt;
  &lt;li&gt;summerwind/actions-runner:v0.18.2&lt;/li&gt;
  &lt;li&gt;quay.io/brancz/kube-rbac-proxy:v0.8.0&lt;/li&gt;
  &lt;li&gt;docker:dind&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The only image that could not be pulled transparently with our setup was the one from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;quay.io&lt;/code&gt;: this registry was not mirrored transparently, which meant that the label was different in Artifactory. As an initial fix I choose to override the image name &lt;strong&gt;manually&lt;/strong&gt; after it has been deployed, using this command:&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl &lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;image deployment/controller-manager kube-rbac-proxy&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;registry.artifactory.mydomain.com/brancz/kube-rbac-proxy:v0.8.0 &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; actions-runner-system
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This means that the controller-manager deployment gets a new image assigned that has the name kube-rbac-proxy and it will reload that container. After that, things actually started running and I could the runner to be available on either the organization or repository level.&lt;/p&gt;

&lt;h1 id=&quot;docker-in-docker-dind-with-internal-certs&quot;&gt;Docker in docker (DinD) with internal certs&lt;/h1&gt;
&lt;p&gt;Our Rancher setup used an internal proxy to pull our images from an Artifactory server that was signed with an internal certificate (without a full trust chain). This meant that the Docker client used to pull the images had to be configured to trust the internal certificate as well or you only got pull errors with an untrusted cert. To accomplish this I build a new DinD container on our runners that where running on a VM and had the certificates installed locally.&lt;/p&gt;

&lt;p&gt;Action.yml that build the image:&lt;/p&gt;
&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v2&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Build&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;cd dind&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;# certs on RHEL are found here:&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;cp -R /etc/pki/ca-trust/source/anchors/ certificates/&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;docker build -t ${DIND_NAME}:${TAG} -f Dockerfile --build-arg http_proxy=&quot;$http_proxy&quot; --build-arg https_proxy=&quot;$http_proxy&quot; --build-arg no_proxy=&quot;$no_proxy&quot; .&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So that we could load the local &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;certificates&lt;/code&gt; folder into the image (note that you can also used the commented RUN command to hardcode a specific certificate):&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;FROM docker:dind

&lt;span class=&quot;c&quot;&gt;# Add the certs from the VM we are running on to this container for secured communication with Artifactory&lt;/span&gt;
COPY /certificates /etc/ssl/certs/

&lt;span class=&quot;c&quot;&gt;# add the crt to the local certs in the image and call system update on it:&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#RUN cat /usr/local/share/ca-certificates/docker_registry.crt &amp;gt;&amp;gt; /etc/ssl/certs/ca-certificates.crt&lt;/span&gt;
RUN update-ca-certificates
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;note-tested-with-docker_tls_certdir-as-well-didnt-work&quot;&gt;Note: tested with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$DOCKER_TLS_CERTDIR&lt;/code&gt; as well: didn’t work&lt;/h2&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Add the certs to this image for secured communication with Artifactory&lt;/span&gt;
COPY docker_registry.crt &lt;span class=&quot;nv&quot;&gt;$DOCKER_TLS_CERTDIR&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# Docker should load the certs from here, didn&apos;t work&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;note-tested-with-daemonjson-as-well-didnt-work&quot;&gt;Note: tested with daemon.json as well: didn’t work&lt;/h2&gt;
&lt;p&gt;I also tested by adding a daemon.json&lt;/p&gt;
&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;insecure-registries&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;And then copied that json file over:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;COPY daemon.json /etc/docker/daemon.json &lt;span class=&quot;c&quot;&gt;# see https://docs.docker.com/registry/insecure/,  didn&apos;t work&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Somehow this file seemed to be ignored and pulling the images from the internal registry still failed.&lt;/p&gt;

&lt;h1 id=&quot;loading-the-new-dind-image&quot;&gt;Loading the new DinD image&lt;/h1&gt;

&lt;p&gt;Loading the new DinD image could not be done by the same ‘hack’ as used for the image from quay.io. After reaching out to the &lt;a href=&quot;https://github.com/actions-runner-controller/actions-runner-controller/issues/701&quot;&gt;community&lt;/a&gt; they helped me with actually overwriting the controller-manager deployment with both the image from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;quay.io&lt;/code&gt; and the new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DinD&lt;/code&gt; image:&lt;/p&gt;
&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# controller-manager.yaml:&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;apps/v1&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Deployment&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;control-plane&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;controller-manager&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;controller-manager&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions-runner-system&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;replicas&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;selector&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;matchLabels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;control-plane&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;controller-manager&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;control-plane&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;controller-manager&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;containers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;--metrics-addr=127.0.0.1:8080&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;--enable-leader-election&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;--sync-period=10m&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;--docker-image=registry.artifactory.mydomain.com/actions-runner-dind:latest&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/manager&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;GITHUB_TOKEN&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;valueFrom&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;secretKeyRef&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;github_token&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;controller-manager&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;optional&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;GITHUB_APP_ID&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;valueFrom&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;secretKeyRef&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;github_app_id&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;controller-manager&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;optional&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;GITHUB_APP_INSTALLATION_ID&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;valueFrom&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;secretKeyRef&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;github_app_installation_id&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;controller-manager&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;optional&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;GITHUB_APP_PRIVATE_KEY&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/etc/actions-runner-controller/github_app_private_key&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;summerwind/actions-runner-controller:v0.18.2&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;manager&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;containerPort&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;9443&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;webhook-server&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;protocol&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;TCP&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;resources&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;limits&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;cpu&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;100m&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;memory&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;100Mi&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;requests&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;cpu&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;100m&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;memory&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;20Mi&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;volumeMounts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;mountPath&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/tmp/k8s-webhook-server/serving-certs&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cert&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;readOnly&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;mountPath&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/etc/actions-runner-controller&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;controller-manager&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;readOnly&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;--secure-listen-address=0.0.0.0:8443&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;--upstream=http://127.0.0.1:8080/&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;--logtostderr=true&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;--v=10&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;registry.artifactory.mydomain.com/brancz/kube-rbac-proxy:v0.8.0&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;kube-rbac-proxy&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;containerPort&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;8443&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;https&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;terminationGracePeriodSeconds&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;10&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cert&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;secret&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;defaultMode&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;420&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;secretName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;webhook-server-cert&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;controller-manager&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;secret&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;secretName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;controller-manager&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After that, we could deploy our own Single-runner.yaml, which has an option to specify the image to use for the runner:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# runner.yaml
apiVersion: actions.summerwind.dev/v1alpha1
kind: Runner
metadata:
  name: gh-runner
spec:
  repository: robbos/testing-grounds
  image: registry.artifactory.mydomain.com/actions-runner # overwriting the runner to use our own runner image, the DinD runner comes from the controller
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;runner-fix&quot;&gt;Runner fix&lt;/h1&gt;
&lt;p&gt;I actually had to patch the runner image as well, because our setup had a tmp folder on a different device, which was causing &lt;a href=&quot;https://github.com/actions-runner-controller/actions-runner-controller/issues/686&quot;&gt;errors&lt;/a&gt; during the bootup of the container. I copied the container definition over from the actions-runner-controller repo and fixed the script, published our own image and told the runner deployment to use it by setting the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec.image&lt;/code&gt; as in the example above.&lt;/p&gt;

&lt;h1 id=&quot;other-observations&quot;&gt;Other observations&lt;/h1&gt;

&lt;h2 id=&quot;namespace&quot;&gt;Namespace&lt;/h2&gt;
&lt;p&gt;Something that took me a while to figure out: the namespace &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;actions-runner-system&lt;/code&gt; is hardcoded in all deployment files, so you cannot change it (easily). Keep that in mind if you want to land in a pre existing namespace with internal pod security policies for example.&lt;/p&gt;

&lt;h2 id=&quot;community&quot;&gt;Community&lt;/h2&gt;
&lt;p&gt;The community creating these runner setups is active on both creating the solutions and helping people out. Given that the used setup is actively maintained and they supported me with my questions is a great sign of a good community. Without the community, I would not have been able to get this done, so thanks a lot!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210806/tim-mossholder-bo3SHP58C3g-unsplash.jpg&quot; alt=&quot;Drawing of two hands held up next to each other in lots of colors&quot; /&gt;&lt;/p&gt;
&lt;h6 id=&quot;photo-by-tim-mossholder-on-unsplash&quot;&gt;Photo by &lt;a href=&quot;https://unsplash.com/@timmossholder?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Tim Mossholder&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/s/photos/team?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/h6&gt;
</content>
		</entry>
	
		<entry>
			<title>Azure DevOps: enable project functionality</title>
			<link href="https://devopsjournal.io/blog/2021/05/31/Azure-DevOps-enable-project-functionality"/>
			<updated>2021-05-31T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2021/05/31/Azure-DevOps-enable-project-functionality</id>
			<content type="html">&lt;p&gt;Sometimes you spot interesting things online that you &lt;em&gt;have&lt;/em&gt; to figure out 😎.
This time it was a tweet from Martin Ehrnst:&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;&lt;a href=&quot;https://twitter.com/AzureDevOps?ref_src=twsrc%5Etfw&quot;&gt;@AzureDevOps&lt;/a&gt; i&amp;#39;m using your API to create new projects. However, I would like to provision these without services like boards. I cannot find any way to to do this. Doesn&apos;t the API support this?&lt;/p&gt;&amp;mdash; Martin Ehrnst ☁️ (@ehrnst) &lt;a href=&quot;https://twitter.com/ehrnst/status/1395638309515313154?ref_src=twsrc%5Etfw&quot;&gt;May 21, 2021&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;p&gt;In Azure DevOps you can enable or disable features on a per-project basis:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210531/20210531_SettingsOverview.png&quot; alt=&quot;Screenshot of Azure DevOps Settings Overview page on the project level&quot; /&gt;&lt;/p&gt;

&lt;p&gt;After some reverse engineering I found out that you can request and set the state of these features by calling into the API.&lt;/p&gt;

&lt;h1 id=&quot;getting-the-feature-state&quot;&gt;Getting the feature state&lt;/h1&gt;
&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;POST: https://dev.azure.com/{ORGANIZATION}/_apis/FeatureManagement/FeatureStatesQuery/host/project/{PROJECTID}?api-version=4.1-preview.1&lt;/code&gt;
Indicating in the body what you want to know. If you only pass in 1 featureId, you only get the state for that single feature.&lt;/p&gt;
&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;featureIds&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ms.vss-work.agile&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ms.vss-code.version-control&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ms.vss-build.pipelines&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ms.vss-test-web.test&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ms.feed.feed&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;featureStates&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;scopeValues&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;project&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;{PROJECTID}&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;setting-the-feature-state&quot;&gt;Setting the feature state&lt;/h1&gt;
&lt;p&gt;You can set the state of a feature by sending in a PATCH request for a single feature. Posting for multiple features in one go doesn’t seem to work. This makes sense since the API also does it on a per feature basis.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PATCH: https://dev.azure.com/{ORGANIZATON}/_apis/FeatureManagement/FeatureStates/host/project/{PROJECTID}/{featureId}?api-version=4.1-preview.1&lt;/code&gt;
With body:&lt;/p&gt;
&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;featureId&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ms.feed.feed&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;scope&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;userScoped&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;settingScope&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;project&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;state&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Where featureId from the body needs to match the featureId in the URL (find the list in the example above).&lt;/p&gt;

&lt;h1 id=&quot;observations&quot;&gt;Observations&lt;/h1&gt;
&lt;p&gt;To bad you cannot pass in these settings through the API when you create a project. At least there is another way to get things working 😄.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Solidify: Using GitHub Actions Securely</title>
			<link href="https://devopsjournal.io/blog/2021/05/28/Solidify-show-Using-GitHub-Actions-Securely"/>
			<updated>2021-05-28T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2021/05/28/Solidify-show-Using-GitHub-Actions-Securely</id>
			<content type="html">&lt;p&gt;Today I got to deliver my session “Using GitHub Actions Securely” at the Solidify show, hosted by our friends at &lt;a href=&quot;https://solidify.dev&quot;&gt;Solidify&lt;/a&gt;. A nice virtual community session during lunch in my time zone (CEST) with people joining in, even from Kuala Lumpur!&lt;/p&gt;

&lt;p&gt;I got a couple of questions during the session that I wanted to dive deeper into and address them here, as well as sharing the slides and the recording of it.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210528/20210528_MyOctocat.png&quot; alt=&quot;Image of myoctocat.com&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The session has been recorded and you can re-watch it on &lt;a href=&quot;https://www.youtube.com/watch?v=C58DSezZFR8&quot;&gt;YouTube&lt;/a&gt; or here:&lt;/p&gt;

&lt;iframe width=&quot;900&quot; height=&quot;508&quot; src=&quot;https://www.youtube.com/embed/C58DSezZFR8&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h1 id=&quot;slide-deck&quot;&gt;Slide deck&lt;/h1&gt;
&lt;p&gt;I you want to look up some things from the slides or visit one of the many links in there, you can look at the slide deck &lt;a href=&quot;/slides/20210528%20GitHub%20Actions%20security%20Solidify.pdf&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id=&quot;question-can-you-prevent-a-reviewer-to-approve-their-own-changes-on-a-pull-request&quot;&gt;Question: Can you prevent a reviewer to approve their own changes on a Pull Request?&lt;/h1&gt;
&lt;p&gt;Full question: “I would like to enforce that at least two people are involved with every change made to a protected branch.
GitHub by default allows a reviewer to approve their own changes to a PR, which makes it possible to use any open PR to merge changes possibly for making an attack on the production environment.
Do you have any guidance how to avoid such a scenario?”&lt;/p&gt;

&lt;p&gt;You can use branch policies to set up rules that need to be met before a Pull Request be merged:
&lt;img src=&quot;/images/2021/20210528/20210528_BranchProtectionRules.png&quot; alt=&quot;Screenshot of branch protection rules&quot; /&gt;&lt;/p&gt;

&lt;p&gt;With the default setup people with higher access levels can still merge the pull request:
&lt;img src=&quot;/images/2021/20210528/20210528_BranchProtectionRules2.png&quot; alt=&quot;Example of the result of the previous branch protection rule&quot; /&gt;
I see two options to still enforce that someone else needs to review the PR:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Add a minimum of 2 reviewers: then the PR author always needs to review the PR their self as well as an extra reviewer.&lt;/li&gt;
  &lt;li&gt;Add a check that verifies that there was an extra reviewer, next to the PR author. You can create that check with a workflow that posts back the status after a reviewer has approved the PR.&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;question-would-you-recommend-actions-always-to-executed-in-containers&quot;&gt;Question: Would you recommend actions always to executed in containers?&lt;/h1&gt;
&lt;p&gt;Yes! As much as you can: that gives you a nice extra security boundary for the stuff you actions are doing: instead of running those processes on the runner process itself, it will run them in a container. This is also a good way to prevent stuff from being installed on the runner machine: for example, you can use the OpenJDK image to build your JAVA project, without installing JAVA on the host machine. You get an extra security layer on top of it as well. Breaking out of a container can probably still be done, but is a bit harder to do. This also helps with anything else remaining on the machine, like files on disk, stuff in the environment variables. You can read more in the ephemeral runners section below.&lt;/p&gt;

&lt;h1 id=&quot;question-is-it-possible-to-run-the-gh-actions-in-self-managed-kubernetes-cluster-in-cloud&quot;&gt;Question: Is it possible to run the GH Actions in Self Managed Kubernetes cluster in Cloud?&lt;/h1&gt;
&lt;p&gt;Yes there is! There are multiple community projects available to run the &lt;a href=&quot;https://docs.github.com/en/actions/hosting-your-own-runners&quot;&gt;runners&lt;/a&gt; in a scalable, self hosted way. The default is installing the agent your self on a VM of your choosing. If you want to add autoscaling into the mix, and get ephemeral runners as an extra benefit, you can look into this &lt;a href=&quot;https://github.com/jonico/awesome-runners&quot;&gt;list of curated community solutions&lt;/a&gt; for hosting them on your own infrastructure, for example Kubernetes. I’ve used one option myself that I’ll add the experiences below.&lt;/p&gt;

&lt;h1 id=&quot;lets-first-explain-ephemeral-runners&quot;&gt;Lets first explain ephemeral runners:&lt;/h1&gt;
&lt;p&gt;An ephemeral runner is only spun up for a specific workflow run, and only exists for the duration of the run. Afterwards it is deleted and cannot be used anymore. This is of course an ideal setup to use with Kubernetes: when you need capacity (a workflow is scheduled): spin up a container with the workflow runner inside of it, register it with GitHub, and execute the workflow.
Doing this with a Virtual Machine is a lot more time consuming and difficult to do, even with the cloud. If you really have the need, you could use it that way.&lt;/p&gt;

&lt;p&gt;Using an ephemeral runner also means that there is no option for state (files stored on disk, environment variables) to linger on the environment that could potentially be picked up by another workflow that executes on the same runner.&lt;/p&gt;

&lt;h1 id=&quot;actions-runner-controller&quot;&gt;Actions runner controller&lt;/h1&gt;
&lt;p&gt;The solution I’ve been testing things out is the &lt;a href=&quot;https://github.com/actions-runner-controller/actions-runner-controller&quot;&gt;Actions runner controller&lt;/a&gt;. This one is spun up as a controller on Kubernetes (I used &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/aks?WT.mc_id=AZ-MVP-5003719&quot;&gt;AKS&lt;/a&gt; for it) that periodically checks with the GitHub API if there are jobs waiting in the queue. If so, it will spin up a new container with the runner inside of it, register it with GitHub and let it do the work for you. After the job is done, the container will be removed and deleted.  The container will execute the workflow itself, or leverage &lt;a href=&quot;https://jpetazzo.github.io/2015/09/03/do-not-use-docker-in-docker-for-ci/&quot;&gt;Docker in docker&lt;/a&gt; to create a container if your actions need them.&lt;/p&gt;

&lt;p&gt;This works really nicely: I’ve hammered it with 6 workflows in the same repository that spun up over 20 jobs at the same time: scaling kicked in and created multiple runners for me. After the completion there is a cool off period, where the runners are being removed completely after 10 minutes (the default setting).&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Pester tests: moving from v4 to v5</title>
			<link href="https://devopsjournal.io/blog/2021/05/25/Moving-pester-to-version-5"/>
			<updated>2021-05-25T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2021/05/25/Moving-pester-to-version-5</id>
			<content type="html">&lt;p&gt;This one took me way to many trials and searches to figure out, so I wanted to store it here in case I need it later on.
Maybe someone else will find this useful as well 😄.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210525/20210525_PesterHeader.png&quot; alt=&quot;Pester site header image&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;the-premise&quot;&gt;The premise&lt;/h1&gt;
&lt;p&gt;We have a pipeline for &lt;a href=&quot;https://www.globaldevopsbootcamp.com&quot;&gt;GDBC&lt;/a&gt; from June 2019 that uses &lt;a href=&quot;https://pester.dev&quot;&gt;Pester&lt;/a&gt; tests written in PowerShell to verify the outcome of our pipeline: we create (a lot of) resources in Azure and Azure DevOps and want to check if they actually exists.&lt;/p&gt;

&lt;p&gt;We run the tests inside a PowerShell task in Azure DevOps and install the Pester module with this:&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;Install-Module&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Pester&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Force&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-SkipPublisherCheck&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Import-Module&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Pester&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This of course installs the latest version. After moving things to a different environment, I skipped the tests for a while (booh!) and yesterday decided to add them back. And lo and behold: things where not working anymore.&lt;/p&gt;

&lt;p&gt;The tricky part was getting things to work with a Pester file that holds parameters that we need to pass into it.&lt;/p&gt;

&lt;h2 id=&quot;pester-file-has-parameters&quot;&gt;Pester file has parameters&lt;/h2&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kr&quot;&gt;param&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$region&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$pathToJson&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$runDirectory&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h1 id=&quot;parameters-we-need&quot;&gt;Parameters we need:&lt;/h1&gt;
&lt;p&gt;To set things up, we need to set the parameters with some values to use:&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$region&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;LocalDevOpsBootcamp&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$dataFilePath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;c:\temp&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$datafilename&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;data-999.json&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;pester-40&quot;&gt;Pester 4.0&lt;/h1&gt;
&lt;p&gt;With the previous version of Pester we called Pester and added a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Data&lt;/code&gt; object to pass in the variable values.&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;Invoke-Pester&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
 &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Script&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;GDBC-AzureDevopsProvisioning.Tests.ps1&apos;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
 &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Data&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;@{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;region&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;$region&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pathToJson&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;$dataFilePath/$datafilename&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;runDirectory&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;AzureDevOps-provisioning&apos;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
 &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-OutputFile&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Test-Pester.XML&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
 &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-OutputFormat&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;NUnitXML&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;error-running-the-v4-setup-against-v5&quot;&gt;Error running the v4 setup against v5&lt;/h2&gt;
&lt;p&gt;This gave the following warning / error with version 5:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;WARNING: You are using Legacy parameter set that adapts Pester 5 syntax to Pester 4 syntax. This parameter set is deprecated, and does not work 100%. The -Strict and -PesterOption parameters are ignored, and providing advanced configuration to -Path (-Script), and -CodeCoverage via a hash table does not work. Please refer to https://github.com/pester/Pester/releases/tag/5.0.1#legacy-parameter-set for more information.

System.Management.Automation.RuntimeException: No test files were found and no scriptblocks were provided.

 at Invoke-Pester&amp;lt;End&amp;gt;, C:\Program Files\WindowsPowerShell\Modules\Pester\5.2.1\Pester.psm1: line 5082
 at &amp;lt;ScriptBlock&amp;gt;, D:\a\_temp\272537fd-8fdd-42fc-b176-803d9ca859d6.ps1: line 7
 at &amp;lt;ScriptBlock&amp;gt;, &amp;lt;No file&amp;gt;: line 1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;pester-5&quot;&gt;Pester 5.*&lt;/h1&gt;
&lt;p&gt;This is the part that took me way to long to figure out. You can run Pester with a container by calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Invoke-Pester -Container $container&lt;/code&gt; and add the parameters to pass along to the test.
That is step 1.&lt;/p&gt;

&lt;p&gt;If you also want to add settings, you need to &lt;strong&gt;wrap the container in a configuration&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;So the steps are:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Create a container&lt;/li&gt;
  &lt;li&gt;Add the parameters &lt;strong&gt;and&lt;/strong&gt; the testfile(s) to the container&lt;/li&gt;
  &lt;li&gt;Create a configuration&lt;/li&gt;
  &lt;li&gt;Add your settings on the configuration&lt;/li&gt;
  &lt;li&gt;Set the container as the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;run&lt;/code&gt; in the configuration&lt;/li&gt;
  &lt;li&gt;Run Pester with the configuration&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;example&quot;&gt;Example&lt;/h2&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# create a new container that will be executed&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$container&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;New-PesterContainer&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
 &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Path&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;./gdbc2019-provisioning/AzureDevOps-provisioning/GDBC-AzureDevopsProvisioning.Tests.ps1&apos;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
 &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# include the parameters to pass into the Pester file&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
 &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Data&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;@{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;region&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;$region&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pathToJson&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$dataFilePath&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$datafilename&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;runDirectory&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;./gdbc2019-provisioning/AzureDevOps-provisioning&apos;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# create a new configuration with our settings&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$config&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;New-PesterConfiguration&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;TestResult&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;OutputFormat&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;NUnitXML&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;TestResult&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;OutputPath&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Test-Pester.XML&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;TestResult&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Enabled&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$True&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# configure the run with the new container from step 1&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Run&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Container&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$container&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# actually call Pester&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Invoke-Pester&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Configuration&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$config&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;More information can be found here:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://pester-docs.netlify.app/docs/commands/New-PesterConfiguration&quot;&gt;PesterConfiguration&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://pester.dev/docs/usage/data-driven-tests&quot;&gt;Data driven tests with Pester&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
		</entry>
	
		<entry>
			<title>Techorama: Using GitHub Actions Securely</title>
			<link href="https://devopsjournal.io/blog/2021/05/18/Techorama-Using-GitHub-Actions-Securely"/>
			<updated>2021-05-18T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2021/05/18/Techorama-Using-GitHub-Actions-Securely</id>
			<content type="html">&lt;p&gt;Today I got to deliver my &lt;a href=&quot;https://techorama.nl/speakers/session/how-to-secure-your-github-actions/&quot;&gt;session&lt;/a&gt; “Using GitHub Actions Securely” at Techorama, my favorite conference. I could feel the pressure at the start of the session: this is Techorama, so you need to deliver this one top notch!&lt;/p&gt;

&lt;p&gt;I think I had some viewers (wasn’t visible to me) and I got a couple of questions during the session that I wanted to dive deeper into and address them here.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210518/20210518_MyOctocat.png&quot; alt=&quot;Image of myoctocat.com&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I think the sessions have been recorded, but will only be shared with the attendees of the conference. So if you weren’t there…&lt;/p&gt;

&lt;h1 id=&quot;slide-deck&quot;&gt;Slide deck&lt;/h1&gt;
&lt;p&gt;I you want to look up some things from the slides or visit one of the many links in there, you can look at the slide deck here:
&lt;a href=&quot;/slides/2021/20210518%20GitHub%20Actions%20security%20Techorama.pdf&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id=&quot;question-running-sast-tools-on-pull-requests-from-forks&quot;&gt;Question: Running SAST tools on pull requests from forks&lt;/h1&gt;
&lt;p&gt;Full question: Secrets are not shared with forks and Action runs from forks cannot use secrets from your repo. How do you run SAST (SonarCloud) on pull requests from forks?
&lt;strong&gt;Answer:&lt;/strong&gt;
This was around the part where I was talking about the dangers of running a workflow on a Fork from a Pull Request on a public repository.
&lt;img src=&quot;/images/2021/20210518/20210518_ForkTriggers.png&quot; alt=&quot;Image of workflow triggers as play: pull_request_target most importantly&quot; /&gt;
If you want to know more about this topic, follow that link to &lt;a href=&quot;https://xpir.it/gh-pwn-request&quot;&gt;https://xpir.it/gh-pwn-request&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can still run a SAST tool on a pull request. Just don’t blindly run it on a pull request from a forked repo: you don’t now what changes someone made before creating the PR.&lt;/p&gt;

&lt;p&gt;What you &lt;strong&gt;can&lt;/strong&gt; do, is run a basic workflow with the pull_request trigger that does the security scans on any dependency you want to verify. If that one succeeds (add enough checks to have a feeling of trusting the changes), add a label to the Pull Request.
That label can then trigger a secondary workflow that does the execution of the SAST tool. This secondary workflow can then run with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pull_request_target&lt;/code&gt; trigger AND the existence of the label. Since adding a label can only be done by a maintainer of the repository, so that is a good safety measure to take.
Additionally you can use &lt;a href=&quot;https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners&quot;&gt;code owners&lt;/a&gt; in the repository to trigger an additional notification to for example your security engineer in the team to have an extra look. For example when the PR changes something in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.github&lt;/code&gt; folder, like a workflow file.&lt;/p&gt;

&lt;h1 id=&quot;question-is-a-reputation-check-commentstarts-a-good-enough-validation-for-github-actions&quot;&gt;Question: Is a reputation check (comment/starts) a good enough validation for GitHub Actions&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt; you can &lt;strong&gt;try&lt;/strong&gt; to use the reputation check together with the name of the publisher of an action to gauge some things around a GitHub Action. For example, you can see the community involvement with that Action. But really checking if the Action is being maintained can only be done by looking into issues and following the discussions there. It’s probably good to check if the Action has been created by the vendor of the tool it’s targeting. If the vendor themselves is maintaining it, they have their name attached to it and hopefully they see that as a responsibility to keep their action up to date and as a way to engage the community to enable people to use their product more. So the cloud Actions from Google, AWS and Microsoft Azure are probably good enough to use.&lt;/p&gt;

&lt;p&gt;The problem here is that still, you are pulling in code from the internet and you should check what that code is actually doing. Will you really trust the publisher on their blue eyes that they will not screw things up? And after all, we are still humans, so a mishap is a regular thing 😄.&lt;/p&gt;

&lt;p&gt;So in my opinion: trust is good, but verification is better.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Don&apos;t use self signed certificates on GitHub Enterprise</title>
			<link href="https://devopsjournal.io/blog/2021/05/16/Dont-use-self-signed-certificates-on-GitHub-Enterprise"/>
			<updated>2021-05-16T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2021/05/16/Dont-use-self-signed-certificates-on-GitHub-Enterprise</id>
			<content type="html">&lt;p&gt;Often you come across an organization that has a policy to use self signed certificates on internal services: as long as you control the workstations used to connect to them, that is a solution that might work. Sometime you still run into issues from them and they usually have a workaround available. Maybe IT-services likes to be in control who can create and hand out certificates that way. In a real DevOps environment I’d want the team to have control over them, and even automate their deployment and refresh them automatically in a regular interval.&lt;/p&gt;

&lt;h1 id=&quot;github-enterprise-server&quot;&gt;GitHub Enterprise Server&lt;/h1&gt;
&lt;p&gt;For GitHub Enterprise server, this has so many downsides that I really recommend &lt;strong&gt;not&lt;/strong&gt; using self signed certificates on that: get a proper cert with a full certification chain on it. Let’s go over the things that you will run into when you install a self signed certificate:&lt;/p&gt;

&lt;h2 id=&quot;users-cannot-connect-to-github-enterprise&quot;&gt;Users cannot connect to GitHub Enterprise&lt;/h2&gt;
&lt;p&gt;Browsers will give you errors if the certificate on the site is not trusted on the machine. This happens with a self signed cert that does not have a full certification chain. Fixing this one is easy: if IT already has self signed certificates setup throughout the internal services, they will also have a way to add a new certificate to the internal trust stores. Those are distributed to the workstations and servers and therefor all clients connecting will have no issues doing so.&lt;/p&gt;

&lt;h2 id=&quot;github-actions-on-virtual-machines&quot;&gt;GitHub Actions on Virtual Machines&lt;/h2&gt;
&lt;p&gt;When you start using actions in GitHub Enterprise (available in version 3.0), you’ll probably start with &lt;a href=&quot;https://docs.github.com/en/actions/hosting-your-own-runners/adding-self-hosted-runners&quot;&gt;self hosted runners&lt;/a&gt; as well: there was a reason for hosting your own GitHub Enterprise server, so internal runners will come next. For starters lets assume you are running them on a virtual machine (VM).&lt;/p&gt;

&lt;p&gt;When you create the VM, you now also need to trust the self signed certificate into the VM. If you don’t do that, then you cannot use the &lt;a href=&quot;https://github.com/marketplace/actions/checkout&quot;&gt;checkout action&lt;/a&gt; on the VM: when it does a Git Checkout it will use the the remote url to get the code from the repository: that will use the self signed certificate that it doesn’t trust. If there is no Git installed on the VM, it will execute an HTTPS fetch from the repository, that will need the self signed certificate that it doesn’t trust.&lt;/p&gt;

&lt;p&gt;Alright, this also has a straightforward fix: trust the self signed certificate on the runners VM (please automate the creation of the VM’s and the installation of the runner 😉).&lt;/p&gt;

&lt;p&gt;You can load the local Linux cert directory if the action is running on a VM by adding this setting to the action:&lt;/p&gt;
&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;SSL_CERT_DIR&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/etc/ssl/certs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Or use the node settings to load a specific cert if the action is build on node:&lt;/p&gt;
&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Checkout&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v2&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;NODE_EXTRA_CA_CERTS&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/etc/ssl/certs/ca-bundle.crt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The downside of this is that you need to add these workaround into each and every workflow that users create (and tell them they need to).&lt;/p&gt;

&lt;h2 id=&quot;github-actions-running-docker-containers&quot;&gt;GitHub Actions running Docker containers&lt;/h2&gt;
&lt;p&gt;A good security practice to protect your runners, is to use Docker containers to run Actions you don’t (want to) trust: run them inside a container as an extra security boundary to &lt;a href=&quot;/blog/2021/02/07/GitHub-Actions-Security-Private-Runners&quot;&gt;limit the access&lt;/a&gt; the runner user has on your VM. If you have setup the runner with least-privileges then the container boundary is hard to break out of.&lt;/p&gt;

&lt;p&gt;If you start running Actions or jobs in a container, you are now bound to the certificate trust chain &lt;strong&gt;of the container&lt;/strong&gt; and you are no longer running in the context of the runner that reads it’s certificate trust chain from the host. That means you will find it hard to use the self signed certificate. From something like a call to an internal NPM endpoint (with a self signed certificate as well), you could go all-in and just ignore the full SSL certificate completely by adding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NODE_TLS_REJECT_UNAUTHORIZED = 0&lt;/code&gt; to your environment. This is highly insecure and will open up your environment to a person-in-the-middle attack. I’m sure that is not what you want?&lt;/p&gt;

&lt;p&gt;You could also add the file onto the container (volume mount it or download it as an artefact) and add it to NPM so it can find it:&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm config &lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;cafile &lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;path to your certificate file&amp;gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What you now have accomplished is that each and every developer who wants to use your internal service with a self signed certificate needs to add extra steps to the workflows to get them to work, adding a lot of extra friction. If you had added a certificate with a full trust chain, things would work out of the box, without any workarounds.&lt;/p&gt;

&lt;h2 id=&quot;start-the-runner-with-skip-validation-setting&quot;&gt;Start the runner with skip validation setting&lt;/h2&gt;
&lt;p&gt;You can also start the workflow runner with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--sslskipcertvalidation&lt;/code&gt; which is another bad practice opening you up to a person-in-the-middle attack. Please don’t use it!&lt;/p&gt;

&lt;h2 id=&quot;mount-the-certificate-store&quot;&gt;Mount the certificate store:&lt;/h2&gt;
&lt;p&gt;Another option is to mount the cert store in the job that is running the container:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;self-hosted&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;node:10.16-jessie&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/etc/ssl/&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Checkout will happen inside the container&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Checkout current repo&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This will still mean your DevOps engineers will need to remember to think about the volume mount when using Actions. Even worse: depending on the base-image they are using, the volume to mount even might be different!&lt;/p&gt;

&lt;h2 id=&quot;make-your-own-base-images&quot;&gt;Make your own base images&lt;/h2&gt;
&lt;p&gt;A better (and safer option all around in my opinion) would be to create your own base images that the DevOps engineers can use: lock down and harden those images any way you want, and then install your own certificate in the image as well!&lt;/p&gt;

&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion:&lt;/h1&gt;
&lt;p&gt;You can find workarounds for the GitHub runner / action or runner to be able to get things working. A lot of workarounds have impacts that for example mean you need to include them in each and every workflow you want to run. The easier way then is to get a proper certificate setup on your GitHub Enterprise server: now things will work as intended and you don’t have to tell your users to add the workaround to their workflows.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>My working / presenting from home setup</title>
			<link href="https://devopsjournal.io/blog/2021/05/13/home-setup"/>
			<updated>2021-05-13T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2021/05/13/home-setup</id>
			<content type="html">&lt;p&gt;I got some questions recently on my office setup at home, so it might be helpful to share it here as well. I am lucky to have the means to invest in my setup, even with a supporting employer who helps out with some of the financials since we all need to work from home these days instead of being at the customer’s office. Some time ago I figured we’d be working this way for a while (due to the COVID pandemic) and it seemed like a good idea to invest in the place I work the most in: my home office.
Even after the pandemic is over (hopefully) and people have more freedom to go into their work offices, the plan is to still work 2 or 3 days from home: I really don’t have the wish to go back to the major traffic congestions we have in The Netherlands. That means that these investments will be used for a while and that helps with some of the decisions that where made. Even my partner agreed on that, hence why some of this stuff is over the top awesome 😁.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210513/20210513_HowItStarted.jpeg&quot; alt=&quot;Photo of my office setup before and after the investments&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The image on the left displays my working from home setup somewhere in April 2020: we just moved to a new house and I was not planning to have an office space at all. Then we went into the first lockdown and I was forced to set something up. So, a couple of moving boxes, a piece of board and a tablecloth was what I made my makeshift desk with. Our offices where so kind to lend a good chair and an extra screen if you needed it.&lt;/p&gt;

&lt;h1 id=&quot;the-gear&quot;&gt;The gear&lt;/h1&gt;
&lt;p&gt;Let’s talk about the gear you see in the image 😄.&lt;/p&gt;

&lt;h2 id=&quot;monitor&quot;&gt;Monitor&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2021/20210513/20210513_Screen.jpg&quot; alt=&quot;Close up of the monitor&quot; /&gt;&lt;br /&gt;
The monitor is a &lt;a href=&quot;https://www.coolblue.nl/product/825247/dell-ultrasharp-u3419w.html&quot;&gt;Dell U3419W&lt;/a&gt;. It’s a 34 inch ultrawide curved screen with Quad HD (1440p). This is one of the more expensive items: I was searching for a better solution then my laptop on a stand and an extra screen. When I was a developer I remembered being very fond of having 3 21 inch monitors side to side, and having the different resolutions of my laptop and an extra screen from the office weren’t good enough. I researched monitors and found an option of around €500, since that was the amount of money I felt comfortable spending. When talking things over with my wife of the extra stuff I wanted, she told me to get something good since that is what you are looking at for over eight hours a day. I still felt a little uncomfortable spending this much on a monitor. After setting it up I am very happy with it! Really glad I could spend the extra money on it and have an awesome setup. You don’t notice the curve and the resolution is excellent (3440 x 1440).&lt;/p&gt;
&lt;h5 id=&quot;sidenote-couldnt-find-a-link-on-the-dell-site-itself-so-i-used-this-dutch-web-shop-that-i-like&quot;&gt;Sidenote: couldn’t find a link on the Dell site itself so I used this Dutch web shop that I like.&lt;/h5&gt;

&lt;p&gt;I am using this monitor with a Picture-By-Picture (PBP) setup: that means that I can plugin two cables to my laptop and the laptop thinks it is working with two different screens. I really wanted this setup that way since I work remotely in such a way that I often share my screen. Screensharing an ultrawide with a resolution of 3440 x 1440 can be done, but the other side of the video chat will not be able to read anything you are displaying, unless they have a similar resolution available on their end.
You can of course just share just a small window, but as a hands-on DevOps consultant I often share my entire screen so I can use different windows on it. Having this PBP option enables me to have two screens available with each a resolution of 1720 x 1440. It is a rather strange resolution because of the height. Luckily this works well enough to not be a real hassle in my day to day usage.&lt;/p&gt;

&lt;h2 id=&quot;lights&quot;&gt;Lights&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2021/20210513/20210513_Light.jpg&quot; alt=&quot;Close up of the lights&quot; /&gt;
The lights are &lt;a href=&quot;https://www.elgato.com/en/key-light-air&quot;&gt;Elgato KeyLight Airs&lt;/a&gt;. They have options for their brightness and you can set the color from ultrabright white to really yellowish:
&lt;img src=&quot;/images/2021/20210513/20210513_Elgato_Light_Strip.png&quot; alt=&quot;Image of the Elgato Control Center&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I need them so people can actually see me during a video meeting, usually the brightness is set really low (15%), just good enough to balance out the light coming from the sky light in the roof. The color temperature has been set to 5150 and I usually don’t change that.&lt;/p&gt;

&lt;p&gt;The photo’s below are taken on a cloudy day. When the sun is out (and especially in the mornings are flooding in through the window) I really need an extra light. So from left to right: without any lights (somewhat okay today), with only one light on, and then with both lights on.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210513/LightsExample.png&quot; alt=&quot;3 different photos with and without lights&quot; /&gt;
I have them both setup a bit higher on my screen to prevent to much eye strain from having a bright light in my eyes all day. That is also the reason why I have the brightness on 15%: I find that is good enough on most days and still doesn’t bother me to much. Keep in mind that I sometimes have them on for hours in a row, with some breaks in between.&lt;/p&gt;

&lt;h2 id=&quot;cameras&quot;&gt;Camera’s&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2021/20210513/20210513_C922.jpg&quot; alt=&quot;Close up of the Logitech C922&quot; /&gt;
During the start and summer of 2020 good webcams where very hard to find, since there was a lot going on with production (of both chips and camera’s) being stalled due to the pandemic. When I finally was able to grab on, I picked the &lt;a href=&quot;https://www.logitech.com/en-us/products/webcams/c922-pro-stream-webcam.960-001087.html&quot;&gt;Logitech C922 Pro&lt;/a&gt; both for it’s price and the fact that it did 1080p at 60fps. Additional benefit was that it has a great microphone. Since the only thing I had available at that time was the one on my headsets (&lt;a href=&quot;https://www.sony.com/electronics/support/wireless-headphones-bluetooth-headphones/wh-1000xm2&quot;&gt;Sony WH-1000XM2&lt;/a&gt;) or the standard iPhone headset) or 🤢 the build in one on my laptop (Dell XPS9500), this was a very welcome addition at the time.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210513/20210513_Brio.jpg&quot; alt=&quot;Close up of the Logitech Brio&quot; /&gt;
Later on I got a &lt;a href=&quot;https://www.logitech.com/en-us/products/webcams/brio-4k-hdr-webcam.960-001105.html&quot;&gt;Logitech Brio&lt;/a&gt;. This one is a full 4K webcam, has HDR, can be zoomed (with the right tools) and even has &lt;a href=&quot;https://docs.microsoft.com/en-us/windows-hardware/design/device-experiences/windows-hello-face-authentication?WT.mc_id=DOP-MVP-5003719&quot;&gt;Windows Hello&lt;/a&gt; support!&lt;/p&gt;
&lt;h5 id=&quot;do-note-that-the-brio-has-a-worse-microphone-then-the-c922-the-later-has-an-awesome-stereo-microphone-that-really-performs-better-then-the-simple-one-on-the-brio-since-i-am-not-using-either-at-the-moment-it-doesnt-matter-for-me-right-now&quot;&gt;Do note that the Brio has a worse microphone then the C922: the later has an awesome stereo microphone that really performs better then the simple one on the Brio. Since I am not using either at the moment, it doesn’t matter for me right now.&lt;/h5&gt;

&lt;p&gt;The cool thing I have now, is that I can use two different viewpoints by leveraging the dual camera’s that I have: the Brio sits on top of my monitor for normal video conferencing, the C922 is to my left and shows off the microphone better, together with my cool GitHub Popfilter 😍 (was a gift). I use the &lt;a href=&quot;https://obsproject.com/forum/resources/obs-virtualcam.949/&quot;&gt;OBS Virtual Camera&lt;/a&gt; setup to switch between camera’s.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210513/20210513_C922Photo.jpg&quot; alt=&quot;Photo from the Logitech C922&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;headset&quot;&gt;Headset&lt;/h2&gt;
&lt;p&gt;Music is very important for me. Often I’ve got something playing during the day, in meetings and even presentations. I really can get a lot of energy from music.&lt;/p&gt;

&lt;p&gt;I actually have multiple headsets to use. A couple of years ago I treated myself to a &lt;a href=&quot;https://www.sony.com/electronics/support/wireless-headphones-bluetooth-headphones/wh-1000xm2&quot;&gt;Sony WH1000XM2&lt;/a&gt;: over the ear headset. It gives excellent audio and has one of the best (at that time, get the fourth generation now) noise-cancelation. The battery duration is great as well. I think I’m easily using that for more then a week these days before I need to charge it (which does take four hours btw).
Only thing that this second generation misses is the dual connection option: the fourth generation has it, where you can connect to multiple devices at once, like for example a phone and a laptop at the same time. Currently I sometimes miss phone calls, since the noise cancellation is that good 😁.&lt;/p&gt;

&lt;p&gt;Also note that after a couple of hours, my ears get really warm and I don’t like that. So I usually switch to a different output option.&lt;/p&gt;

&lt;p&gt;As a backup I have a &lt;a href=&quot;https://www.jabra.nl/bluetooth-headsets/jabra-elite-25e##100-98400000-60&quot;&gt;Jabra Elite 25e&lt;/a&gt;. This one is in-ear, has good noise-cancelation but still lets you hear some environment noise. The only thing I don’t like on these is that the plugs that are included are to large and pressure my ears to much.It comes with 3 different sizes and even the smallest one still hurts my (large) ears after an hour or so. Still, it is wireless, blue tooth and has good audio.&lt;/p&gt;

&lt;p&gt;As the ultimate backup, I have the original iPhone headset with an earphone jack. That’s not wireless of course, but still has good audio and I can wear it for a couple of hours.&lt;/p&gt;

&lt;p&gt;So in the event something isn’t working or a battery is empty, I can still work.&lt;/p&gt;

&lt;p&gt;For the last few weeks I have been using the build-in speakers of my monitor more and more, if the kids are not at home and I don’t need the noise-cancellation I like that my ears are free.
(it also doesn’t help that my new laptop is on Windows Insider (Dev Channel) and currently has issues connection any blue-tooth headset 😲)
Together with a great microphone I can still communicate without to much interference being picked up from the speakers. The speakers do a well enough job, although the sound on my Sony is a lot better 😀.&lt;/p&gt;

&lt;h2 id=&quot;microphone&quot;&gt;Microphone&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2021/20210513/20210513_Microphone.jpg&quot; alt=&quot;Close up of the Yeticaster&quot; /&gt;
As mentioned before, I used the microphone on my C922 for a couple off months. During that period I really wanted a better one that I could move closer to my mouth for that deeper radio voice 🎤. I had to wait for some months to actually be able to order the one I wanted, since that happened to be out of stock at the time as well 😱. I finally landed on a set of both microphone and flexible arm in one: the &lt;a href=&quot;https://www.bluemic.com/en-us/products/yeticaster/&quot;&gt;yeticaster&lt;/a&gt;. It is the yeti microphone, the Radius III (shockmount) and Compass (flexible arm). The arm is what drew me in the most: a lot of flexible arms have an open construction with the springs visible. I don’t like the look of that and I’ve read that those springs can wear out (which can then still be replaced). This arm uses internal springs with friction control on the parts where it bends. It looks very slick and smooth and even adjusts really well.&lt;/p&gt;

&lt;h2 id=&quot;desk&quot;&gt;Desk&lt;/h2&gt;
&lt;p&gt;At the beginning of the pandemic I needed a desk fast and wasn’t expecting to need it for a long time. I still liked the standing desks we have at the office, so I really wanted a standing desk. Look around and most of the shops where closed and only Ikea had the option to remotely order one and pick it up. So I picked an &lt;a href=&quot;https://www.ikea.com/nl/nl/p/skarsta-bureau-zit-sta-wit-s89324812/&quot;&gt;Ikea Skarsta&lt;/a&gt; desk, with a manual lever since I found the premium for the electric version to much. How often do you stand during the day anyway? For me that was once or twice, and I could use the two minutes of exercise 😀. What was I wrong on this one! The desk wobbles a lot and I don’t stand often enough because of this: lowering and raising the table makes everything wobble. Some of the placement of the lamps doesn’t really help currently as well: since I am very tall, the desk needs to be raised to its full length (1.20m) and the left lamp hits the sloped ceiling. I need some more space on the right to shift things over, but I am still looking for a better way to stash my laptop somewhere: I have it either on my desk or on a stack of stuff on the right of my desk.&lt;/p&gt;

&lt;p&gt;The reason for this is that the laptop produces a lot of heat and having it flush on the desk is not giving it enough air to breathe. I’d rather have it hanging under my desk or in a good open laptop drawer. I bought a great drawer, but made a mistake by reading the inner size and measuring that out. Unfortunately the outer sizes don’t find under the desk 🙁. If you have a better recommendation: please let me know! I’d rather not see it at all, so under the desk would be great. Another issue with that is that I haven’t managed to get my Dell XPS 9500 to sleep and then wake up with something. I’ve tried the on-power trigger (doesn’t work since I use the 90W power over USB-C coming off of the monitor. Dell thinks that is not a power supply, and underwatted even since it wants 130W).&lt;/p&gt;

&lt;h2 id=&quot;led-strips&quot;&gt;Led strips&lt;/h2&gt;
&lt;p&gt;The led strip is mostly for myself and is wrapped around the edges of my desk. It is an &lt;a href=&quot;https://www.elgato.com/en/light-strip&quot;&gt;Elgato Light Strip&lt;/a&gt;. You can set them from ultra-bright to fully dimmable, choose both the lumen and the RGB color you want to use. The control center for it is awesome and works within the boundaries of my WiFi network, which is good from a privacy standpoint. It also has support for a phone app, which I use all the time.&lt;/p&gt;

&lt;p&gt;Windows Control Center:
&lt;img src=&quot;/images/2021/20210513/20210513_Elgato_Light_Strip.png&quot; alt=&quot;Image of the Elgato Control Center&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I bought this one to try it out and light the wall behind me, since it has the flexibility and it can be shortened. I’m a fan of Elgato stuff, since they take good care of designing their products to look smooth, work great and even take a lot of care around packaging of them as well.&lt;/p&gt;

&lt;p&gt;For the wall behind me it is a little bit short and the LED’s are to large: you would probably see them in my video feed. So now I have it wrapped around the desk (it handles a 90 degree bend perfectly) and light up the wall behind my desk.
It curls a bit at the end that doesn’t have the power supply, but a small nail, pin or some extra glue will probably fix that (it doesn’t curl any further right now and I don’t see it, so it doesn’t bother me at all).&lt;/p&gt;

&lt;h2 id=&quot;keyboard--mouse&quot;&gt;Keyboard / Mouse&lt;/h2&gt;
&lt;p&gt;For my keyboard and mouse I use the same one I got 3 years ago for my consultancy setup: I (used to) travel a lot and still wanted to have an ergonomic keyboard. That’s why I still use a &lt;a href=&quot;https://www.microsoft.com/en-us/p/microsoft-sculpt-ergonomic-desktop/8xk02kz6k69w?activetab=pivot%3aoverviewtab&quot;&gt;Microsoft Sculpt Ergonomic Desktop&lt;/a&gt;. I don’t like the sculped mouse that is in the set, that’s why I only got the keyboard itself. I do use the numpad these days as an easy toggle in OBS for its &lt;a href=&quot;https://obsproject.com/forum/resources/obs-virtualcam.949/&quot;&gt;Virtual Camera&lt;/a&gt;. This way I can easily switch in any meeting tool that we happen to use at that time. (Seriously, I think I have most of them installed 😱).&lt;/p&gt;

&lt;h2 id=&quot;laptop&quot;&gt;Laptop&lt;/h2&gt;
&lt;p&gt;As a laptop I use the one our company provides us with. They let us choose a new one every 2 - 2,5 years and you have all options with a couple of manufacturers. When I got started at &lt;a href=&quot;https://xpirit.com&quot;&gt;Xpirit&lt;/a&gt; I choose a Dell XPS 9500, with 32 gigs of RAM and a 1 Terabyte SSD. This was one with the up-your-nose-camera at the bottom of the screen. Always fun during video meetings 😄.&lt;/p&gt;

&lt;p&gt;Just recently I got to order a new one and now I have a &lt;a href=&quot;https://www.dell.com/nl-nl/work/shop/dell-laptops-en-notebooks/nieuw-xps-17/spd/xps-17-9700-laptop/bnx79708&quot;&gt;Dell XPS 9700&lt;/a&gt;, again with 32 gigs of RAM and a Terabyte SSD. Luckily this one has the camera on the top of the screen and even has &lt;a href=&quot;https://docs.microsoft.com/en-us/windows-hardware/design/device-experiences/windows-hello-face-authentication?WT.mc_id=DOP-MVP-5003719&quot;&gt;Windows Hello&lt;/a&gt; support, both with the camera and a fingerprint reader.&lt;/p&gt;

&lt;h2 id=&quot;headset-stand&quot;&gt;Headset stand&lt;/h2&gt;
&lt;p&gt;I was doubting between a wireless charger to place my phone on and a separate headset stand to place my headset on. I didn’t want a separate charging pad on my desk and wanted to stop laying the headset flat on the desk everyday. So this seemed like a good compromise. It works really great! It’s a &lt;a href=&quot;https://www.bol.com/nl/p/urgoods-headset-stand-koptelefoon-standaard-houder-met-draadloze-oplaad-functie-kabel-organizer/9300000023772597/?s2a=&quot;&gt;URGOODS&lt;/a&gt; headset stand.&lt;/p&gt;

&lt;h1 id=&quot;update-2022&quot;&gt;Update 2022&lt;/h1&gt;
&lt;p&gt;In the beginning of 2022 I started automating some things in my office to have less things to turn on and off 😁. Writing the &lt;a href=&quot;https://github.com/rajbos/home-automation&quot;&gt;repo readme&lt;/a&gt; with all the setup triggered me to update this post with the new stuff. (Yes, lot’s of stuff will turn on and off ‘by itself’ now).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210513/SetupUpdate2022.jpg&quot; alt=&quot;Photo of the current desk setup&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;speakers&quot;&gt;Speakers&lt;/h2&gt;
&lt;p&gt;In December of 2021 I’ve added a set of speakers to the setup: I didn’t like the ones from the monitor, especially on calls there was something annoying with the base from it. It supports Bluetooth, dual RCA and a normal headphone jack (which I use for my laptop). After looking around (and asking colleagues who had opinions 😁) I settled on the &lt;a href=&quot;https://www.coolblue.nl/product/814826/edifier-r1700bt-2-0-pc-speaker.html&quot;&gt;Edifier R1700BT 2.0 Pc Speaker&lt;/a&gt;. It is a really nice set for this price, and is not to big on the desk as well. I also like that they are very forgiving for shutting on and off by just switching off the power through the power cable (instead of the switch on the back). That helped with turning them on and off from some &lt;a href=&quot;https://github.com/rajbos/home-automation&quot;&gt;home automation&lt;/a&gt; scripts!&lt;/p&gt;

&lt;h2 id=&quot;do-epic-shit-signal&quot;&gt;Do Epic Shit signal&lt;/h2&gt;
&lt;p&gt;We have an internal motto at &lt;a href=&quot;https://xpirit.com&quot;&gt;Xpirit&lt;/a&gt; that tells us go do out and ‘Do Epic Shit’ (which is whatever you deem epic), with a logo, T-shirts and since the last holidays an ‘Do Epic Shit’ signal as well! This is linked to an Azure IOT setup with a service bus that can push messages to us (by using the light as &lt;a href=&quot;https://en.wikipedia.org/wiki/Morse_code&quot;&gt;morse code&lt;/a&gt; signals). Fun thing to have, and it lights up the place!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210513/DoEpicShit.jpg&quot; alt=&quot;Photo of the Do Epic Shit signal: a light in the form of a turd that spells out the words &apos;Do epic shit&apos;&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;what-is-next&quot;&gt;What is next?&lt;/h1&gt;
&lt;p&gt;What I still really want is a good way to have the laptop running somewhere, with enough air to breathe. As long as it is not &lt;strong&gt;on&lt;/strong&gt; the desk but hidden somewhere, preferably with a remote boot option, so I don’t need to physically press the power button.&lt;/p&gt;

&lt;p&gt;Next I’d like a good docking station, to support both of my laptops in case I need to switch (I currently mostly remote into the old one if I need something). Since I’m currently working for a customer that has company issued laptops, I often need to switch all my gear between two laptops. That currently is a lot of cable wrangling (both of them have different ports setup, so I have different USB hubs for them). Ideally I’d want one that can drive the two screens on my monitor, supports a lot of USB/USB-C plugs (I have eight or so in use right now), and can connect (and power) my laptops with just one USB-C cable. That way switching between my own laptop and the one from the customer is just switching one cable.&lt;/p&gt;

&lt;p&gt;The last thing that I’d like is something to light up the wall behind me with different colors. Something like the &lt;a href=&quot;https://nanoleaf.me/en-US/products/nanoleaf-shapes/#nanoleaf-shop&quot;&gt;Nanoleaf&lt;/a&gt; would be really nice!&lt;/p&gt;

&lt;p&gt;Example of the nanoleaf on a wall behind a monitor:
&lt;img src=&quot;/images/2021/20210513/20210513_Nanoleaf.png&quot; alt=&quot;Nanoleaf example on wall&quot; /&gt;&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>GitHub and Azure DevOps: best of both worlds</title>
			<link href="https://devopsjournal.io/blog/2021/04/23/GitHub-and-Azure-DevOps-best-of-both-worlds"/>
			<updated>2021-04-23T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2021/04/23/GitHub-and-Azure-DevOps-best-of-both-worlds</id>
			<content type="html">&lt;p&gt;Ever since Microsoft acquired GitHub we have been looking at how this will play out in the long run with their Azure DevOps offering. We’ve seen a lot of people move from the Azure DevOps team to GitHub by looking at their LinkedIn or other social network updates, so it only makes sense to wonder about the future. We know that several &lt;strong&gt;very large&lt;/strong&gt; teams inside Microsoft are using Azure DevOps, together with some really large customers, which means Azure DevOps will stick around for a long time and will still receive regular updates to its functionality. Azure DevOps has a very strong offering, from ‘everything in one suite’ to mix and match with third party tools, to a good hosting model (data sovereignty is a big thing for us Europeans) and excellent Azure support with even integrated billing on the marketplace.&lt;/p&gt;

&lt;p&gt;GitHub on the other hand is the largest dev-community in the world, with some &lt;a href=&quot;https://octoverse.github.com/&quot;&gt;56 million plus&lt;/a&gt; developers on it. If you want to entice developers to work at your company, doing so with the tools they already know and love is an excellent selling point. GitHub is made by developers, for developers. This also gives them a lot of information from open source communities into their activity, languages used and even a great host of information they can pull from the open source code stored on the platform. They are now even hosting &lt;a href=&quot;https://github.blog/changelog/2019-05-23-maintainer-security-advisories/&quot;&gt;security advisories&lt;/a&gt; and enable scanning for vulnerability patterns in your codebase with &lt;a href=&quot;https://github.blog/2021/2021-03-16-using-github-code-scanning-and-codeql-to-detect-traces-of-solorigate-and-other-backdoors/&quot;&gt;CodeQL&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210423/20210423AzDo_and_GitHub.png&quot; alt=&quot;Azure DevOps and GitHub logos together&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;exploring-best-of-both-worlds&quot;&gt;Exploring best of both worlds&lt;/h1&gt;
&lt;p&gt;In this post I want to share what are the strengths of both offerings and explain what parts I’d use of each of them and why. In my opinion both products have some unique features that the other doesn’t have (yet), so using the right tool for the right job is only logical from that. Luckily the integration between both is very good, so mixing and matching is a great option here.&lt;/p&gt;

&lt;p&gt;I’ve split things up in the following sections:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Source control&lt;/li&gt;
  &lt;li&gt;Work tracking&lt;/li&gt;
  &lt;li&gt;Pipelines&lt;/li&gt;
  &lt;li&gt;Artifacts&lt;/li&gt;
  &lt;li&gt;Security features&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;source-control&quot;&gt;Source control&lt;/h1&gt;
&lt;p&gt;In the basis, source control on both platforms is very similar: Azure DevOps still has &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/repos/tfvc/what-is-tfvc?view=azure-devops&amp;amp;WT.mc_id=DOP-MVP-5003719&quot;&gt;TFVC support&lt;/a&gt;, but I really don’t recommend using that at all: Git is the standard these days and for good reasons (that I’ll skip over for this post 😁).
Both platforms have branch tracking, pull requests, a branch protection system, a good CLI to work with, so choosing what to use here is comes down to the extra features on top of the basics. That is where GitHub has the better options in my opinion because it offers these features:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;GPG commit signing&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.github.com/en/github/administering-a-repository/managing-a-branch-protection-rule&quot;&gt;Branch protection rules&lt;/a&gt; with GPG commit signing&lt;/li&gt;
  &lt;li&gt;Out of the box security features like code scanning&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;gpg-commit-signing&quot;&gt;GPG Commit signing&lt;/h2&gt;
&lt;p&gt;Commit signing is an extra layer of security on top of basic authentication. Without using commit signing it is rather easy to make some code changes for you, commit them locally to the repository and then push them upstream. I’ve automated parts of this lots of times know, so I know how easy it is to create. Running the software on you machine, scanning the repos and adding some malicious stuff to your projects is rather easy and you would not even know that it was happening right under your nose 😄. What’s even worse, I could even run this on my own machine and push in code changes with &lt;strong&gt;your&lt;/strong&gt; email address and user name attached to it. The configuration in Git is just meta data that is not verified anywhere in the chain: GitHub only checks if the user pushing the changes has write access to the repository and then lets the changes in. This is of course the standard for all source control systems using Git.&lt;/p&gt;

&lt;p&gt;With &lt;a href=&quot;https://www.gnupg.org/gph/en/manual/x110.html&quot;&gt;commit signing&lt;/a&gt;, you need to sign the commit with your private key that only you should have. By default, that key can only be used together with a passphrase (you can create it without a passphrase, but that would defeat its purpose). Adding the passphrase means that when you sign the commit, you have to type in the passphrase. There is no way to automatically fill in the passphrase for you (that I know of). This also means you cannot sign the commits automatically, which means someone cannot spoof your signage on your behalf.&lt;/p&gt;

&lt;p&gt;The server side (GitHub in this case) will store the public part of your GPG key that they use to verify the signed commits: those are made by users with GPG signatures AND those signatures have been verified with their accompanying public key. This enables you to lock down the repository or specific branches in the repository to only allow verified commits.
&lt;img src=&quot;/images/2021/20210423/20210423_VerifiedCommit.png&quot; alt=&quot;Screenshot of GitHub.com showing a verified commit&quot; /&gt;.&lt;/p&gt;

&lt;h1 id=&quot;work-tracking&quot;&gt;Work tracking&lt;/h1&gt;
&lt;p&gt;Work tracking has always been great in Azure DevOps and I think it still is the better offering. GitHub has several options to enable some of the same setup, with great integration into source control with &lt;a href=&quot;https://docs.github.com/en/github/writing-on-github/autolinked-references-and-urls#issues-and-pull-requests&quot;&gt;issues and pull requests&lt;/a&gt;. You can even add subtasks to an issue with &lt;a href=&quot;https://docs.github.com/en/github/managing-your-work-on-github/about-task-lists&quot;&gt;task lists&lt;/a&gt;, although I like the Epic setup with subtasks better from &lt;a href=&quot;https://azure.microsoft.com/en-us/services/devops/boards?WT.mc_id=DOP-MVP-5003719&quot;&gt;Azure Boards&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;available-in-both-offerings&quot;&gt;Available in both offerings&lt;/h2&gt;
&lt;p&gt;Let’s look into what is available in both offerings:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Rich text setup (with markdown) that includes emoji, gifs and even video’s in most fields.&lt;/li&gt;
  &lt;li&gt;Easy way to create new items (even with CLI and API’s)&lt;/li&gt;
  &lt;li&gt;@Mention other users, issues or pull requests in the text of a work item / issue&lt;/li&gt;
  &lt;li&gt;Discussing on the work that needs to be done&lt;/li&gt;
  &lt;li&gt;Notification system for items that mention you&lt;/li&gt;
  &lt;li&gt;Labeling and searching on them&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note: what GitHub doesn’t have is the whole formal setup with approvals, adding in extra fields, adding story points (and summarizing them). Some of it can be replicated if you want. Personally I don’t like all that extra stuff, it’s unnecessary process ceremony in an effort to feel in control over the agile process, while it only brings extra gates to jump through without adding the correct value. That might be a different blog post 😄.&lt;/p&gt;

&lt;h2 id=&quot;githubs-projects-vs-azure-boards&quot;&gt;GitHub’s Projects vs Azure Boards&lt;/h2&gt;
&lt;p&gt;GitHub has created &lt;a href=&quot;https://docs.github.com/en/github/managing-your-work-on-github/managing-project-boards&quot;&gt;project boards&lt;/a&gt; in an effort to give us more control over issues then just the list it is by itself. It basically adds a kanban setup to track the issues through different stages. By default this is just ‘To do’, ‘In progress’ and ‘Done’. This works great in an &lt;a href=&quot;https://en.wikipedia.org/wiki/Open-source_software&quot;&gt;OSS&lt;/a&gt; project where you don’t operate in regular sprints (who knows when you have the time and energy to work on things). If you are working in an organization that has sprints, you need something to define them. That currently isn’t available in GitHub: what they’ve created for it is &lt;a href=&quot;https://docs.github.com/en/github/managing-your-work-on-github/about-milestones&quot;&gt;Milestones&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;By adding an issue to a milestone, you can track their state over a longer time period and view how much work is still left before the milestone is reached. The general idea comes again from the OSS where this works great: you create issues for things you find or features you want to add, mark them as part of a milestone and work together towards that goal.&lt;/p&gt;

&lt;p&gt;You can use them for sprints as well, where a milestone can be seen as a sprint and (open) issues would be added to a milestone. That way they can be tracked over time and be used for planning. Only thing I don’t like about this setup is the naming: a milestone is not a sprint: usually what we see as a milestone can easily take multiple sprints to achieve.&lt;/p&gt;

&lt;p&gt;On the other hand: I more and more dislike the idea of sprints: we humans are not meant to keep on sprinting all the time: from that we experience time like a treadmill where we run until we cannot run any longer. I feel like we should be working towards a milestone, with short iterations, delivering updates to production as soon as they are done and then enable the new features to the user when we feel like it is working ‘good enough’.&lt;/p&gt;

&lt;p&gt;In conclusion: we should be able to work with Milestones if have a simpler process, skip things like story points and think differently about how we work as a team.&lt;/p&gt;

&lt;h1 id=&quot;azure-pipelines-vs-github-actions&quot;&gt;Azure Pipelines vs GitHub Actions&lt;/h1&gt;
&lt;p&gt;Automation for Continuous Integration (CI) and Continuous Delivery (CD) is available in both platforms. Azure DevOps has the benefit of being available for years now, whereas GitHub Actions only went GA for GitHub Enterprise in the beginning of 2021. GitHub Actions is catching up fast, but it is still catching up.&lt;/p&gt;

&lt;p&gt;Azure DevOps has a robust and powerful setup for CI/CD: you can use the classic pipelines or yaml pipelines (preferred) and mix and match after your own preference. You can have a CI pipeline for fast validation on a branch, and a more extensive one for creating the artefact that you want to deploy. There are lots of extensions available on the &lt;a href=&quot;https://marketplace.visualstudio.com/azuredevops&quot;&gt;marketplace&lt;/a&gt;, so you don’t have to reinvent the wheel. I just checked my Twitter bot &lt;a href=&quot;https://twitter.com/azdomarketnews&quot;&gt;@AzDoMarketNews&lt;/a&gt; and there are currently 1630 extensions available, with new ones still being added every once in a while.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210423/danil-shostak-AChwtt3tBPU-unsplash.jpg&quot; alt=&quot;Image of watch gears&quot; /&gt;&lt;/p&gt;
&lt;h5 id=&quot;photo-by-danil-shostak-on-unsplash&quot;&gt;Photo by &lt;a href=&quot;https://unsplash.com/@max010?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Danil Shostak&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/s/photos/gears?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/h5&gt;

&lt;h3 id=&quot;big-things-that-are-currently-missing-on-github-actions-are&quot;&gt;Big things that are currently missing on GitHub Actions are:&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Templates&lt;/li&gt;
  &lt;li&gt;Security scans on GitHub Actions&lt;/li&gt;
  &lt;li&gt;Better CI/CD story&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;missing-in-github-actions-templates&quot;&gt;Missing in GitHub Actions: Templates&lt;/h2&gt;
&lt;p&gt;The ability to use a template to group steps so you don’t have to repeat them everywhere. Think of a common template to use for an NPM pipeline for example: often you want to run the same steps in there, like dependency scanning, linting, building and running tests. GitHub has a setup for &lt;a href=&quot;https://docs.github.com/en/actions/creating-actions/creating-a-composite-run-steps-action&quot;&gt;composite actions&lt;/a&gt;, but you cannot include other actions in them, only shell scripts. Same thing goes for defining something like a ‘stage’ in a deployment pipeline: I want to deploy my artefact on Dev/Test in the exact same way as to Production, with only some parameter changes targeting the correct cloud environment: currently, you have to repeat the same steps in both stages of the deployment. Adding a step in between means adding them in multiple places, or you risk drift between the environments.
Azure DevOps has the better story here because they support &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/pipelines/process/templates?view=azure-devops??WT.mc_id=DOP-MVP-5003719&quot;&gt;templates&lt;/a&gt; in your yaml pipelines. This helps you with eliminating duplicate code from your pipeline.&lt;/p&gt;

&lt;h2 id=&quot;missing-in-github-actions-security-scans-on-github-actions&quot;&gt;Missing in GitHub Actions: Security scans on GitHub Actions&lt;/h2&gt;
&lt;p&gt;Currently there is no real way to trust the actions you use, other then trusting their publisher. In Azure DevOps there was a process to get an action published on the marketplace that at least included some &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/extend/publish/overview?view=azure-devops#publish-an-extension&amp;amp;?WT.mc_id=DOP-MVP-5003719&quot;&gt;protective scans&lt;/a&gt; before the extension was available. You also had to create a publisher account and only if you had the correct &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/marketplace/how-to/grant-permissions?view=azure-devops&amp;amp;?WT.mc_id=DOP-MVP-5003719&quot;&gt;permissions&lt;/a&gt; you could install the extension into your tenant, where it would become available for everyone in the organization to use.&lt;/p&gt;

&lt;p&gt;For GitHub Actions the story is a lot simpler, which also makes it less secure. The work to verify what the extension is doing, and keeping up to date with them (using the latest version is against the best &lt;a href=&quot;/blog/2021/02/06/GitHub-Actions-Forking-Repositories&quot;&gt;practice&lt;/a&gt;) can take up time for your team. &lt;strong&gt;Anyone&lt;/strong&gt; with write access to the repository can include new actions in the workflows (the thing that executes your actions). &lt;strong&gt;Anyone&lt;/strong&gt; with a public GitHub repo can create an action.yml file in their repository and you can use it in your workflow. Without any validation by GitHub. Therefore it is really important to lock down your organization and think about the actions you want to allow. It is critical to have a &lt;a href=&quot;/blog/2021/02/06/GitHub-Actions-Forking-Repositories&quot;&gt;process&lt;/a&gt; around GitHub Actions to still enable the DevOps engineers to run them, but in the safest way possible.&lt;/p&gt;
&lt;h5 id=&quot;note-im-still-looking-for-a-way-to-leverage-a-codeql-scan-on-the-action-repository-with-all-the-incoming-changes-included-as-a-check-on-the-issue-ive-seen-that-github-can-run-them-on-all-actions-in-their-system-and-then-send-in-pull-requests-fixing-any-issues-it-found-i-would-really-want-to-run-that-as-an-extra-security-scan-as-well&quot;&gt;Note: I’m still looking for a way to leverage a CodeQL scan on the action repository with all the incoming changes included as a check on the issue. I’ve seen that GitHub can run them on all actions in their system and then send in Pull Requests fixing any issues it found. I would really want to run that as an extra security scan as well.&lt;/h5&gt;

&lt;h2 id=&quot;missing-in-github-actions-better-cicd-story&quot;&gt;Missing in GitHub Actions: Better CI/CD story&lt;/h2&gt;
&lt;p&gt;With GitHub Actions you have two options for CI/CD:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Create one yaml file with both CI/CD in them&lt;/li&gt;
  &lt;li&gt;Create a yaml file for CI and a separate file for CD
Both options have the same issue: especially on the CD part: I want to use the same steps to deploy to all my environments. Maybe insert something additionally in a specific test environment, or have an option to run a load test separately, but mostly I want to use the same deployment. Currently this cannot be done in a straightforward manner. You have the option to add an extra workflow for a new environment, and you could clone an existing one, but that is mostly it. I’d want something like &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/pipelines/process/templates?view=azure-devops?WT.mc_id=DOP-MVP-5003719&quot;&gt;Azure DevOps templates&lt;/a&gt; or what GitLab has. In GitLab you can &lt;a href=&quot;https://docs.gitlab.com/ee/ci/yaml/includes.html&quot;&gt;include&lt;/a&gt; a separate yml file as a template, or use &lt;a href=&quot;https://docs.gitlab.com/ee/ci/yaml/README.html#yaml-anchors-for-scripts&quot;&gt;anchors&lt;/a&gt; to reference complete scripts and have a better way to reuse them between multiple stages.&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;artifacts&quot;&gt;Artifacts&lt;/h1&gt;
&lt;p&gt;In terms of artefacts I’m somewhat split:Azure DevOps has a great story around &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/artifacts/?view=azure-devops?WT.mc_id=DOP-MVP-5003719&quot;&gt;artefacts&lt;/a&gt;: the support for having different feeds and even different &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/artifacts/concepts/feeds?view=azure-devops?WT.mc_id=DOP-MVP-5003719&quot;&gt;views&lt;/a&gt; on a feed, where you can upload a package to a Beta feed and then promote them to production is really great. GitHub doesn’t have something like that.&lt;/p&gt;

&lt;p&gt;Same story around &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/artifacts/quickstarts/universal-packages?view=azure-devops&amp;amp;WT.mc_id=DOP-MVP-5003719&quot;&gt;Universal packages&lt;/a&gt;: you can story any file in them, even publish a build script in them for later reuse outside of Azure DevOps for example. GitHub doesn’t have something like that. There is the concept of &lt;a href=&quot;https://docs.github.com/en/github/administering-a-repository/about-releases&quot;&gt;Releases&lt;/a&gt; but that will always download the release as either a zip file or an exe or msi file. Sometimes you need to download the files themselves, without any additional wrapping. There is an item on the roadmap to &lt;a href=&quot;https://github.com/github/roadmap/issues/118&quot;&gt;support this&lt;/a&gt;, but it is not there just yet.&lt;/p&gt;

&lt;h2 id=&quot;docker-images&quot;&gt;Docker images&lt;/h2&gt;
&lt;p&gt;Where GitHub currently shines, is the option to have your Docker images stored in &lt;a href=&quot;https://github.com/features/packages&quot;&gt;GitHub Packages&lt;/a&gt;. This works out of the box (note: I’ve only used this to publish public container images, not sure if you can lock that down easy enough): if someone pulls an image from this feed, it will just work. The automation flow to push images is simple as well: you can do it from a workflow, as I am doing &lt;a href=&quot;https://github.com/rajbos/dependency-updates/blob/main/.github/workflows/pipeline.yml&quot;&gt;here&lt;/a&gt; where I am pushing my images into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ghcr.io/rajbos/local-dependency-updates&lt;/code&gt;. It’s using the GitHub Container Registry for it, with my GitHub user tag as the image publisher.&lt;/p&gt;

&lt;h1 id=&quot;security-features&quot;&gt;Security features&lt;/h1&gt;
&lt;p&gt;This is the killer feature for GitHub at the moment: I really recommend checking this one out and start moving over your code into GitHub as soon as you can. You can still use Azure Boards and Azure Pipelines if you want to: the &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/developer/github/?&amp;amp;WT.mc_id=DOP-MVP-5003719&quot;&gt;integration&lt;/a&gt; between the products is wonderful and just works™️. You can still commit your code and tag issues on either platform: through the integration it knows where to look and will render the correct links in the UI so your users can seamlessly switch to where they need to work.&lt;/p&gt;

&lt;p&gt;Some parts of the security features in GitHub can be replicated inside of Azure DevOps, but only with a lot of elbow grease. GitHub has them available in the product itself. Some of these are free for public repos and not for private repos. You can find more information on that on the &lt;a href=&quot;https://github.com/pricing&quot;&gt;pricing page&lt;/a&gt;. If you are running GitHub Enterprise server, then some of these are available through &lt;a href=&quot;https://docs.github.com/en/github/getting-started-with-github/about-github-advanced-security&quot;&gt;GitHub Advanced Security&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210423/GitHub_octocat_detective.jpg&quot; alt=&quot;Image of the Octocat as a detective&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;dependabot&quot;&gt;Dependabot&lt;/h2&gt;
&lt;p&gt;First off is &lt;a href=&quot;https://docs.github.com/en/code-security/supply-chain-security/enabling-and-disabling-version-updates&quot;&gt;Dependabot&lt;/a&gt;. This a tool that can scan your code and the third party dependencies that you are using. It has support for &lt;a href=&quot;https://docs.github.com/en/code-security/supply-chain-security/configuration-options-for-dependency-updates#package-ecosystem&quot;&gt;a lot of package managers&lt;/a&gt; and even can update the Action versions you use in your workflows! You can set it up to run periodically and it will scan your repository for you. If it finds any updates it will gather the release notes from them and create a new Pull Request for every package. If there already is an open Pull Request for the package, it will update that PR with the latest version, or close the old one and create a new PR. This is a very easy way to keep your dependencies up to date (and believe me it is: I’ve created my own setup &lt;a href=&quot;https://github.com/rajbos/dependency-updates&quot;&gt;here&lt;/a&gt;). If you have good checks in your pipelines, you can even enable &lt;a href=&quot;https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/automatically-merging-a-pull-request&quot;&gt;auto merge&lt;/a&gt; on your repository and automatically close the PR when all checks are passing. This way you’re always up to date with the latest versions of your dependencies, saving you a lot of update and security headaches in the process.&lt;/p&gt;

&lt;h2 id=&quot;security-scanning&quot;&gt;Security scanning&lt;/h2&gt;
&lt;p&gt;When you have Dependabot enabled, you can also enable &lt;a href=&quot;https://docs.github.com/en/code-security/supply-chain-security/about-managing-vulnerable-dependencies&quot;&gt;vulnerability scanning&lt;/a&gt;. With this feature, Dependabot keeps track of your dependencies and the versions that you are using. If there is a new vulnerability found and published on their own &lt;a href=&quot;https://docs.github.com/en/code-security/supply-chain-security/browsing-security-vulnerabilities-in-the-github-advisory-database&quot;&gt;advisory&lt;/a&gt;, Dependabot will alert you of the vulnerability and create a Pull Request on the repo with the issue. It will even tell you if there are multiple repositories with the same vulnerability in them.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210423/20210423_SecurityAdivsory.png&quot; alt=&quot;Security advisory example&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;secret-scanning&quot;&gt;Secret scanning&lt;/h2&gt;
&lt;p&gt;Another great feature that GitHub launched in March 2021 (GA) is &lt;a href=&quot;https://docs.github.com/en/code-security/secret-security/about-secret-scanning&quot;&gt;Secret Scanning&lt;/a&gt;. They have a long list of partners for which they can find secrets (think passwords or access codes) from in your repository &lt;strong&gt;and even automatically revoke those secrets&lt;/strong&gt;. How awesome is that?! You still need to be aware of secrets and could include a process in your pipelines to detect them. This feature automates that and helps you with the biggest issue of committing your secrets into your repository: they should be considered as leaked: you can use &lt;a href=&quot;https://rtyley.github.io/bfg-repo-cleaner/&quot;&gt;Git BFG&lt;/a&gt; to clean the secrets from your repository, but someone might still have an old copy of the repository somewhere and still have the old secrets available. It’s considered much better to revoke those secrets immediately and create new ones.&lt;/p&gt;

&lt;h2 id=&quot;code-scanning-with-codeql&quot;&gt;Code scanning with CodeQL&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.github.com/en/code-security/secure-coding/about-code-scanning&quot;&gt;Code Scanning with CodeQL&lt;/a&gt; is a recent addition to the GitHub offering as well. It only supports a couple of languages right now (see below). It can do very powerful things, like finding user input that is not sanitized before you use it in your code, even if it’s only used three layers deep in your codebase!&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;C/C++&lt;/li&gt;
  &lt;li&gt;C#&lt;/li&gt;
  &lt;li&gt;Go&lt;/li&gt;
  &lt;li&gt;Java&lt;/li&gt;
  &lt;li&gt;JavaScript/TypeScript&lt;/li&gt;
  &lt;li&gt;Python&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are a lot of CodeQL queries available in the &lt;a href=&quot;https://github.com/github/codeql&quot;&gt;public repository&lt;/a&gt; that you can run on your code. There is even one that checks you code for traces of &lt;a href=&quot;https://github.blog/2021/2021-03-16-using-github-code-scanning-and-codeql-to-detect-traces-of-solorigate-and-other-backdoors/&quot;&gt;Solorigate and other backdoors&lt;/a&gt;. You set it up by adding a &lt;a href=&quot;https://docs.github.com/en/code-security/secure-coding/setting-up-code-scanning-for-a-repository&quot;&gt;CodeQL Analysis workflow&lt;/a&gt; in your repository.&lt;/p&gt;

&lt;p&gt;By running it in your workflows, you get notified if it finds problems in your repository. Those notifications are added as additional checks and they can be found as &lt;a href=&quot;https://docs.github.com/en/code-security/secure-coding/triaging-code-scanning-alerts-in-pull-requests&quot;&gt;annotations&lt;/a&gt; on incoming Pull Requests as well.&lt;/p&gt;

&lt;p&gt;All in all, very powerful stuff!&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Bash cheat-sheet for PowerShell devs</title>
			<link href="https://devopsjournal.io/blog/2021/04/11/Bash-Cheat-sheet-for-PowerShell-devs"/>
			<updated>2021-04-11T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2021/04/11/Bash-Cheat-sheet-for-PowerShell-devs</id>
			<content type="html">&lt;p&gt;Coming from years of Windows dev experience to using a Bash shell took a while to know some of the differences between the two. Since I often still run into these things, I thought it would help me and perhaps other people as well if I wrote some of it down. I suspect this will be a work in progress, so this page will receive some updates over time 😁.&lt;/p&gt;

&lt;h1 id=&quot;working-with-variables&quot;&gt;Working with variables&lt;/h1&gt;
&lt;p&gt;Working with variables is a bit different in Bash. Setting a variable is done by using its name:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;if it doesn’t exists, it will be created&lt;/li&gt;
  &lt;li&gt;if it already exists, its value is updated&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;set-variable-example&quot;&gt;Set variable example&lt;/h3&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;myVar&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;12
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;using-the-variable-example&quot;&gt;Using the variable example:&lt;/h3&gt;
&lt;p&gt;To use the variable, you need to add the $ sign in front of it. This means you want to use the value in the variable.
Watch out though! The shell will try to execute whatever is in the variable. So using the same command you are used to in PowerShell will not work:&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$myVar&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# error here: the shell wants to execute the value in the variable, which is 12 in this case.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Example and output:&lt;/strong&gt;
In order to set the variable and then echo it’s value to the prompt, you can use this setup:&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;myVar&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;12
&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$myVar&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# handle the value as a string and output it&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210411/20210411_Variables.png&quot; alt=&quot;Variables example results&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;string-interpolation-in-bash&quot;&gt;String interpolation in Bash&lt;/h1&gt;
&lt;p&gt;String interpolation works differently as well. Injecting the variable into a string using double quotes will interpret it and inject the value in to the string:&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;this is my value &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$myVar&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# this wil inject the value of myVar as a string&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Wrapping the string in single quote will inject &lt;em&gt;the object&lt;/em&gt; into the string and not the value:&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;this is my value &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$myVar&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# this wil inject the object myVar&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Especially when someone is using the single quotes in a script, this has bitten me once or twice: it took some time to figure out why my value wasn’t visible.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210411/20210411_StringInterpolation.png&quot; alt=&quot;String interpolation results&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;fileheaders&quot;&gt;Fileheaders&lt;/h1&gt;
&lt;p&gt;Add a file header (first line) to the file contents to indicate how the shell should run this file. Example:&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/sh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will indicate it needs to feed this file to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sh&lt;/code&gt; command inside the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin&lt;/code&gt; folder.
It will be used when you execute the file from the command line like this:&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.&lt;span class=&quot;se&quot;&gt;\m&lt;/span&gt;yShellscript
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;outputting-to-a-file&quot;&gt;Outputting to a file&lt;/h1&gt;
&lt;p&gt;Sending some output to a file is similar to the use in PowerShell:&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;s2&quot;&gt;&quot;my string value&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; myShellScript &lt;span class=&quot;c&quot;&gt;# this wil always create a new file and set its content&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;&quot;my string value&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; myShellScript &lt;span class=&quot;c&quot;&gt;# this wil add the string to its content&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The thing that always gets me, is that dang string interpolation difference:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210411/20210411_NewFile.png&quot; alt=&quot;Output of this example&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;cls-command&quot;&gt;cls command&lt;/h1&gt;
&lt;p&gt;In bash there is no cls command shortcut. Always use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;clear&lt;/code&gt; to have the same result.&lt;/p&gt;

&lt;h1 id=&quot;lastexitcode-and-returning-it-from-a-script&quot;&gt;LastExitCode and returning it from a script&lt;/h1&gt;
&lt;p&gt;Just like in PowerShell you can use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$?&lt;/code&gt; to check the last exit codes. In the example below I’m deploying a CDK stack to AWS (Infrastructure as code).
And saving the exit code of the npm command so I can check it, log it and return it to the caller of this shell script.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm --silent run cdk -- deploy --require-approval never --context env=${env} --context tag=${TAG} ${stack_name}
exitCode=$?
if [ $exitCode != &quot;0&quot; ]
then
  echo &quot;NPM deploy cdk step failed: $exitCode&quot;
  exit $exitCode
fi
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
		</entry>
	
		<entry>
			<title>GitHub Actions Create: Dockerfile symlinks error</title>
			<link href="https://devopsjournal.io/blog/2021/04/09/GitHub-Actions-Create-Error-on-Dockerfile"/>
			<updated>2021-04-09T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2021/04/09/GitHub-Actions-Create-Error-on-Dockerfile</id>
			<content type="html">&lt;p&gt;I was building a new &lt;a href=&quot;https://docs.github.com/en/actions/creating-actions/about-actions&quot;&gt;GitHub Action&lt;/a&gt; today with a Dockerfile and got a strange error… `unable to prepare context: path “/action” not found.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210409/20210409_01_ErrorMessage.png&quot; alt=&quot;GitHub execution error message&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I was using a Dockerfile in a sub directory, but the &lt;a href=&quot;https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#runs-for-docker-actions&quot;&gt;documentation&lt;/a&gt; indicates this is supported. So, what gives? Is it a mix-up of the file path? I’m testing it on Windows and can build the Dockerfile itself just fine 🤔. Tested with a backslash instead of a slash, but still an error:
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unable to prepare context: unable to evaluate symlinks in Dockerfile path: lstat /path/to/Dockerfile&lt;/code&gt;
&lt;img src=&quot;/images/2021/20210409/20210409_02_ErrorMessage.png&quot; alt=&quot;GitHub execution error message with symlinks&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;the-fix&quot;&gt;The fix&lt;/h1&gt;

&lt;p&gt;On a hunch (this has happened before!), I changed the file ending of the Dockerfile from CRLF into LF (created the file on Windows of-course!) and… 🎉.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210409/20210409_03_Working.png&quot; alt=&quot;Working result with a successful Docker build&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;for-future-reference&quot;&gt;For future reference&lt;/h1&gt;

&lt;ol&gt;
  &lt;li&gt;Check the line endings on your Dockerfile!&lt;/li&gt;
  &lt;li&gt;You can run an image in your GitHub Action in a subdirectory by using the correct path to it. It needs to be relative to the action.yml file!&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# action.yml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Octogotchi&apos;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Octogotchi&apos;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;branding&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;icon &lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;alert-circle&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;blue&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;runs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;using&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;docker&apos;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;action/Dockerfile&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
		</entry>
	
		<entry>
			<title>GitHub Actions &amp; Security: Best practices - One workflow per runner</title>
			<link href="https://devopsjournal.io/blog/2021/03/07/GitHub-Actions-one-workflow-per-runner"/>
			<updated>2021-03-07T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2021/03/07/GitHub-Actions-one-workflow-per-runner</id>
			<content type="html">&lt;p&gt;One important best practice for any Continuous Integration / Continuous Deployment setup is thinking about attack vectors for your setup. One of those vectors is the way you download your third party dependencies. Whether you are using Docker containers or libraries to build your code upon, these dependencies are external to your system. Usually these are pulled in either through a Container Registry (for Docker images) or through a Package Manager. You are downloading those dependencies at build or deploy time from an external source, usually from the internet.&lt;/p&gt;

&lt;p&gt;There are several ways that a malicious actor can try to leverage these dependencies in an effort to compromise your system.&lt;/p&gt;

&lt;p&gt;This post is part of a series on best practices for using GitHub Actions in a secure way. You can find the other posts here:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2021/02/06/GitHub-Actions-Forking-Repositories&quot;&gt;Forking action repositories&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2021/02/07/GitHub-Actions-Security-Private-Runners&quot;&gt;Private runners&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2021/03/07/GitHub-Actions-one-workflow-per-runner&quot;&gt;One runner, one workflow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210307/georg-bommeli-ybtUqjybcjE-unsplash.jpg&quot; alt=&quot;image of locks on a chain&quot; /&gt;&lt;/p&gt;
&lt;h6 id=&quot;photo-by-georg-bommeli-on-unsplash&quot;&gt;&lt;span&gt;Photo by &lt;a href=&quot;https://unsplash.com/@calina?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Georg Bommeli&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/span&gt;&lt;/h6&gt;

&lt;h1 id=&quot;one-workflow-per-runner&quot;&gt;One workflow per runner&lt;/h1&gt;
&lt;p&gt;The best practice here is to only use a runner for one single workflow. You might be tempted to setup a set of machines that are dedicated for all your pipelines and install multiple runners on the same machine. Or to maximize the utilization of your machines by letting &lt;strong&gt;all&lt;/strong&gt; repositories use the same runners throughout your organization.&lt;/p&gt;

&lt;h1 id=&quot;attack-vectors&quot;&gt;Attack vectors&lt;/h1&gt;
&lt;p&gt;Some example attack vectors that you need to think about with this setup are:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Data Theft&lt;/li&gt;
  &lt;li&gt;Data Integrity Breaches&lt;/li&gt;
  &lt;li&gt;Availability&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;data-theft&quot;&gt;Data Theft&lt;/h2&gt;
&lt;p&gt;An action could be siphoning off your data to a third party. This can be anything: from your host information (OS, IP-address, network information, etc.), your software installation, to locally installed certificates, API-keys or SSH keys. For this reason you need to severely limit the user rights the runner is operating with. Under no circumstance give it root access or network administrator access. Limit it to a service account with access to only the bare minimum it needs: does it really need any internal network access, or is an outgoing https connection enough to do its job? Don’t give it access to more of your local disk then the folder it needs to operate on. Only give it write access there and even limit the read access to most of the rest of the disk.&lt;/p&gt;

&lt;p&gt;This is a reason to not blindly use 1 runner and give it access to all your workflows: each workflow should only have access to its own data, and not that of a different workflow. The user the runner is operating on, will have access to everything inside of its workfolder, with all consequences from that access (keep reading below).&lt;/p&gt;

&lt;h2 id=&quot;data-integrity-breaches&quot;&gt;Data Integrity Breaches&lt;/h2&gt;
&lt;p&gt;We all use open source package managers or container images to build on top of. Most package managers and container setups use a local cache to prevent a new download each time you need a package version. If you reuse the machine, you enable the cache to be used by multiple workflows. What if workflow A runs a malicious script or action and overwrites some of your cache? Workflow B (and C and so on) will be using a package from the cache that has been overwritten with perhaps some malicious content. &lt;a href=&quot;http://xpir.it/Solorigate&quot;&gt;Solorigate&lt;/a&gt; was a prime example of this attack vector: one assembly overwritten without anyone noticing with the huge consequences from it.&lt;/p&gt;

&lt;h2 id=&quot;availability&quot;&gt;Availability&lt;/h2&gt;
&lt;p&gt;Having something compromise your runner does not specifically have to mean stealing your data or overwriting it. Another option can be that the malicious actors want to achieve is hurting your availability to use the workflows altogether. One scenario might be that a zero-day attack is found against your application that can be used to do harm to your company or its users. What if an attacker then has the option to flip a switch somewhere on your runner and all of a sudden you cannot deploy the fix for the zero-day? You’re probably very dependent of your pipelines doing the deployment and might not have ‘break the glass’ setup to deploy an update to production.&lt;/p&gt;

&lt;h1 id=&quot;summary&quot;&gt;Summary&lt;/h1&gt;
&lt;p&gt;All examples above are reasons to &lt;strong&gt;not&lt;/strong&gt; reuse your runners for multiple workflows. Limit their access to your machines and network as much as you can and consider setting something up where each execution always gets a new and clean environment, to prevent security issues from it. Using the &lt;a href=&quot;https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners&quot;&gt;hosted runners&lt;/a&gt; from GitHub might even be a better option: GitHub always gives you a new clean instance&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>SonarQube Community Edition - Change default branch name</title>
			<link href="https://devopsjournal.io/blog/2021/02/12/SonarQube-change-branch-community-version"/>
			<updated>2021-02-12T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2021/02/12/SonarQube-change-branch-community-version</id>
			<content type="html">&lt;p&gt;Small tidbit so I could potentially find this later on: If you are running SonarQube yourself with the Community Edition (for a POC for example, otherwise invest in yourself by getting a higher edition!) then you might find this useful.&lt;/p&gt;

&lt;h2 id=&quot;default-branch-name-in-community-edition&quot;&gt;Default Branch name in Community Edition&lt;/h2&gt;
&lt;p&gt;The default branch in SonarQube Community Edition is still locked to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt;. If you want to change that in SonarQube then there is no user interface option available, as is in higher editions under &lt;a href=&quot;https://docs.sonarqube.org/latest/branches/overview/&quot;&gt;Administration –&amp;gt; Branches and Pull Requests&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can use this url to get to the same page for your project and change the name there:
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://sonarqube.server.url/project/branches?id=&amp;lt;your project name&amp;gt;&lt;/code&gt;&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>GitHub Actions &amp; Security: Best practices - Private Runners</title>
			<link href="https://devopsjournal.io/blog/2021/02/07/GitHub-Actions-Security-Private-Runners"/>
			<updated>2021-02-07T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2021/02/07/GitHub-Actions-Security-Private-Runners</id>
			<content type="html">&lt;p&gt;In this post I want to look into Private Runners for your GitHub Workflows and show you some best practices for them.
GitHub Workflows can run on either a GitHub Hosted runner or on your own private runner. For the private runner you can install it on a machine of your choice and you maintain everything on that machine: the tools that you pre-install, the network stack the runner has access too and any storage it is using during its runs.&lt;/p&gt;

&lt;p&gt;This post is part of a series on best practices for using GitHub Actions in a secure way. You can find the other posts here:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2021/02/06/GitHub-Actions-Forking-Repositories&quot;&gt;Forking action repositories&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2021/02/07/GitHub-Actions-Security-Private-Runners&quot;&gt;Private runners&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2021/03/07/GitHub-Actions-one-workflow-per-runner&quot;&gt;One runner, one workflow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210207/sherise-vd-nS3HSEBrcik-unsplash.jpg&quot; alt=&quot;Image of human and dog &apos;runners&apos;&quot; /&gt;&lt;/p&gt;
&lt;h5 id=&quot;photo-by-sherise-vd-on-unsplash&quot;&gt;&lt;span&gt;Photo by &lt;a href=&quot;https://unsplash.com/@iamsherise?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Sherise VD&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/s/photos/running-dogs?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/span&gt;&lt;/h5&gt;

&lt;h1 id=&quot;why-use-a-private-runner&quot;&gt;Why use a private runner?&lt;/h1&gt;
&lt;p&gt;Some organizations have a policy that their builds (or deploys) always need to run in their own (hosted) datacenter. Another reason might be that you need to use some licensed software that you can run only on your own environment. Or maybe you need a GPU setup in your workflow (those aren’t available from GitHub Hosted Runners as far as I know).&lt;/p&gt;

&lt;p&gt;The runner itself is &lt;a href=&quot;https://github.com/actions/runner&quot;&gt;open source&lt;/a&gt; so you can look at the way it works and contribute updates to it. Installation is made very easy: you can go to your repository or organization or enterprise. Go to Settings –&amp;gt; Actions and at the bottom you can find a button to add a new runner.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210207/20210207_AddRunner.png&quot; alt=&quot;Example page of adding a runner&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If you add the runner at the organization level, it will be available to all repositories in that organization.
If you add the runner at the enterprise level, it will be available to all repositories in all organizations in that enterprise.&lt;/p&gt;

&lt;p&gt;Installation consists of executing these steps:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Download the latest zipped version of the runner at &lt;a href=&quot;https://github.com/actions/runner/releases&quot;&gt;https://github.com/actions/runner/releases&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Unzip it&lt;/li&gt;
  &lt;li&gt;Run the config.cmd script with the url of the level to add the runner and a specific token (note you can also get this token trough the GitHub API). The token will be valid for 1 hour&lt;/li&gt;
  &lt;li&gt;Start the runner&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The runner will run with the privileges you provided during installation and will start a long polling outgoing connection over HTTPS to GitHub. It’s an outgoing connection for safety reasons (e.g. corporate firewalls) and goes out over HTTPS to make it as secure as possible. The runner will periodically ask GitHub (I think every minute) if there is work to do, and if not, back off for another minute.&lt;/p&gt;

&lt;h1 id=&quot;best-practices-in-this-post&quot;&gt;Best practices in this post:&lt;/h1&gt;
&lt;ul&gt;
  &lt;li&gt;Limit the access of your private runner&lt;/li&gt;
  &lt;li&gt;Do not use a runner for more than one repository (on an persistent runner)&lt;/li&gt;
  &lt;li&gt;Never use a private runner for your public repositories&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to learn more about hardening your runner environments, you can read the GitHub documentation &lt;a href=&quot;https://docs.github.com/en/actions/learn-github-actions/security-hardening-for-github-actions&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h1 id=&quot;limit-the-access-of-your-private-runner&quot;&gt;Limit the access of your private runner&lt;/h1&gt;
&lt;p&gt;Limit the access of the private runner to an absolute minimum. Think of all the things the processes the runner itself or the actions it will run for your have access to. &lt;strong&gt;Never&lt;/strong&gt; install it with network admin or root rights. Give it just enough rights to only do what it was meant to do.&lt;/p&gt;

&lt;p&gt;Does the runner really need to have access to your database cluster? If not, limit the network access for it (segment the network for example).&lt;/p&gt;

&lt;p&gt;Does the runner really need access to your file share? What could it do with all those files? What if an action scans the share and start sending the interesting bits to an external server? What if the action executes your code, downloads a third party dependency that has been compromised and that starts to do something on your environment?&lt;/p&gt;

&lt;p&gt;What happens if the action breaks the runner sandbox? Could it elevate itself to a more privileged account?&lt;/p&gt;

&lt;p&gt;Best practice: give the runner the absolute least amount of access it needs to the machine it runs on.&lt;/p&gt;

&lt;h1 id=&quot;do-not-use-a-persistent-runner-for-more-than-one-repository&quot;&gt;Do not use a persistent runner for more than one repository&lt;/h1&gt;
&lt;p&gt;This best practice comes from the same practice as before and mostly comes from the methods used in supply chain attacks like for example the recent &lt;a href=&quot;https://xpir.it/Solorigate&quot;&gt;Solorigate&lt;/a&gt; attack. Both the action that is being executed and the third party dependencies it uses (NPM or NuGet packages or all the layers in the Docker image it uses) can store things on the runner machine. Even the runner itself could store these things, although a compromise of the runner is less likely, it could still happen (we’re all humans after all). Context here matters: this is not so relevant for what we call an ephemeral runner: a runner that only exists for the duration of one job (not workflow!). Having data persisted on an ephemeral runner is not possible, since the runner will be deleted afterwards.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;This topic is of course super important for persistent runners: think of a Virtual Machine that is constantly running.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What if a first run downloads a compromised package and stores that in your local package cache? A second run that needs that package, finds it in the package cache and uses that version instead of retrieving the correct one? As in the &lt;a href=&quot;https://xpir.it/Solorigate&quot;&gt;Solorigate&lt;/a&gt; attack, only 1 assembly was overwritten as a starting point of the compromise.&lt;/p&gt;

&lt;p&gt;This problem could even be enlarged by using the same runner for multiple repositories: Package cache poisoning in one repository could be subsequently be used by all other workflows that are executed on the same runner.&lt;/p&gt;

&lt;p&gt;Even worse: installing multiple runners on the same machine in an effort to enable more efficient use of the available resources on the machine: what if they all use the same package cache. NuGet uses a centrally stored cache on the machine level for example…&lt;/p&gt;

&lt;p&gt;So as a best practice: if you really need a runner, create at least a new runner on a new machine for each repo and give it the minimum rights it needs to do its work.&lt;/p&gt;

&lt;p&gt;An even better option is to create a new runner for each run. For example use an &lt;a href=&quot;https://github.com/machulav/ec2-github-runner&quot;&gt;Action&lt;/a&gt; that creates a new runner just for that specific run, or host your runner on &lt;a href=&quot;https://dev.to/jimmydqv/github-self-hosted-runners-in-aws-part-1-fargate-39hi&quot;&gt;AWS Fargate&lt;/a&gt; or even setup auto-scaling &lt;a href=&quot;https://040code.github.io/2020/2020/05/25/scaling-selfhosted-action-runners&quot;&gt;yourself&lt;/a&gt;.&lt;/p&gt;
&lt;h5 id=&quot;note-the-options-above-all-use-aws-for-hosting-i-havent-seen-any-examples-for-this-setup-on-azure-yet-if-you-have-an-azure-example-let-me-know-i-will-add-it-to-the-list-above&quot;&gt;Note: the options above all use AWS for hosting. I haven’t seen any examples for this setup on Azure yet. If you have an Azure Example, let me know: I will add it to the list above.&lt;/h5&gt;

&lt;h1 id=&quot;never-use-a-private-runner-for-your-public-repositories&quot;&gt;Never use a private runner for your public repositories&lt;/h1&gt;
&lt;p&gt;Also mentioned in the &lt;a href=&quot;https://docs.github.com/en/actions/learn-github-actions/security-hardening-for-github-actions&quot;&gt;guidance&lt;/a&gt; from GitHub is that you should not use private runners for public repositories. Depending on your setup, your workflows will be triggered by new commits being pushed to the repository, or by an incoming pull request.&lt;/p&gt;

&lt;p&gt;What if someone with ill-contempt forks your repository, adds code or a dependency that will compromise your setup (or even the workflow itself) and create a new Pull Request on your repository? The workflow will be triggered (hey, they can even add the trigger &lt;strong&gt;for&lt;/strong&gt; you!) and the malicious setup will run on your machine with all the access the runner has. This is considered very dangerous for obvious reasons.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>GitHub Actions &amp; Security: Best practices</title>
			<link href="https://devopsjournal.io/blog/2021/02/06/GitHub-Actions"/>
			<updated>2021-02-06T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2021/02/06/GitHub-Actions</id>
			<content type="html">&lt;p&gt;I’ve been diving into the security aspects of using &lt;a href=&quot;https://github.com/features/actions&quot;&gt;GitHub Actions&lt;/a&gt; and wanted to share some best practices in one place. If you like to get an overview through a presentation setting instead of a blog, you can also find one of my conference sessions on it &lt;a href=&quot;/blog/2021/05/28/Solidify-show-Using-GitHub-Actions-Securely&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210206/jon-moore-bBavss4ZQcA-unsplash.jpg&quot; alt=&quot;Image of locks on a fence&quot; /&gt;&lt;/p&gt;
&lt;h5 id=&quot;photo-by-jon-moore-on-unsplash&quot;&gt;&lt;span&gt;Photo by &lt;a href=&quot;https://unsplash.com/@thejmoore?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Jon Moore&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/s/photos/security?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/span&gt;&lt;/h5&gt;

&lt;h1 id=&quot;setting-up-an-internal-marketplace-for-github-actions&quot;&gt;Setting up an internal marketplace for GitHub Actions&lt;/h1&gt;
&lt;p&gt;All posts eventually lead up to setting up an internal marketplace for GitHub Actions: something that your team can control, prevents random actions from being used and gives you the process to run actual security checks on the actions in use. Learn how to set one up in your organization &lt;a href=&quot;/blog/2021/10/14/GitHub-Actions-Internal-Marketplace&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id=&quot;forking-action-repositories&quot;&gt;Forking action repositories&lt;/h1&gt;
&lt;p&gt;In the post on &lt;a href=&quot;/blog/2021/02/06/GitHub-Actions-Forking-Repositories&quot;&gt;Forking action repositories&lt;/a&gt; I show these best practices:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Verify the code the Action is executing&lt;/li&gt;
  &lt;li&gt;Pinning versions&lt;/li&gt;
  &lt;li&gt;Forking repositories&lt;/li&gt;
  &lt;li&gt;Keeping your forks up to date&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additionally (and especially in an enterprise setting), you’ll want to create your own internal market place for actions. How to set it up and have a good security process around it can be found &lt;a href=&quot;/blog/2021/10/14/GitHub-Actions-Internal-Marketplace&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id=&quot;keeping-your-forks-up-to-date&quot;&gt;Keeping your forks up to date&lt;/h1&gt;
&lt;p&gt;After forking all the actions you want to use, you also have to own the maintenance. I’ve described a good way of keeping your forks up to date &lt;a href=&quot;/blog/2021/02/06/GitHub-Actions-Forking-Repositories#keeping-your-forks-up-to-date&quot;&gt;here&lt;/a&gt;, by making sure you review the incoming changes before you merge them.&lt;/p&gt;

&lt;h1 id=&quot;secure-your-private-runners&quot;&gt;Secure your private runners&lt;/h1&gt;
&lt;p&gt;In the post on &lt;a href=&quot;/blog/2021/02/07/GitHub-Actions-Security-Private-Runners&quot;&gt;Private runners&lt;/a&gt; I explain these best practices:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Limit the access of your private runner&lt;/li&gt;
  &lt;li&gt;Do not use a runner for more than one repository&lt;/li&gt;
  &lt;li&gt;Never use a private runner for you public repositories&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;do-not-reuse-a-runner-ever&quot;&gt;Do not reuse a runner, ever!&lt;/h1&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2021/03/07/GitHub-Actions-one-workflow-per-runner&quot;&gt;One runner, one workflow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;run-your-own-action-in-a-container&quot;&gt;Run your own action in a container&lt;/h1&gt;
&lt;p&gt;To have an additional boundary for your action you can run it inside a container. This also enables you to use something in your container that doesn’t have to be installed on the runner itself: &lt;a href=&quot;/blog/2021/09/12/GitHub-Actions-container-with-powershell&quot;&gt;Run your action in a container&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id=&quot;run-you-runners-in-a-kubernetes-cluster&quot;&gt;Run you runners in a Kubernetes cluster&lt;/h1&gt;
&lt;p&gt;To mitigate a lot of attack vectors from running your runners on a virtual machine (e.g. disk / network access), you can host your self-hosted runners in a &lt;a href=&quot;/blog/2021/08/06/GitHub-runners-on-kubernetes&quot;&gt;Kubernetes&lt;/a&gt; cluster. Then you have ‘ephemeral’ runners that only exist during the execution of your workflow and then are cleaned up.&lt;/p&gt;

&lt;h1 id=&quot;untrusted-input&quot;&gt;Untrusted input&lt;/h1&gt;
&lt;p&gt;An overview from GitHub on &lt;a href=&quot;https://securitylab.github.com/research/github-actions-untrusted-input&quot;&gt;untrusted input&lt;/a&gt;, from the issue title to the commit message, if you act upon them (even just echoing them to the output!), they can be misused and therefor are an attack vector.&lt;/p&gt;

&lt;h1 id=&quot;preventing-pwn-requests&quot;&gt;Preventing pwn requests&lt;/h1&gt;
&lt;p&gt;It used to be the case that you would trigger your workflow by using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pull_request&lt;/code&gt; in the trigger definition. That scope had to much rights, so GitHub has dialed that down. The scope with more rights (to your access token with some write permissions for example) has now been created to be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pull_request_target&lt;/code&gt;. You need to be really careful with &lt;a href=&quot;https://securitylab.github.com/research/github-actions-preventing-pwn-requests&quot;&gt;using that scope&lt;/a&gt;. Best practice here is to use a label for the pull request so you can manually check the PR and authorize its actual execution.&lt;/p&gt;

&lt;p&gt;You need to be very careful with incoming Pull Requests from any fork: potentially they are changing scripts/commands in your workflow or even the workflow itself. There have been examples where a PR was send in that updated the workflow and made good use of the 10 concurrent jobs available for open source projects to generate some bitcoin for the attacker. This is why we can’t have nice things 😲!&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>GitHub Actions &amp; Security: Best practices - Forking Action repositories</title>
			<link href="https://devopsjournal.io/blog/2021/02/06/GitHub-Actions-Forking-Repositories"/>
			<updated>2021-02-06T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2021/02/06/GitHub-Actions-Forking-Repositories</id>
			<content type="html">&lt;p&gt;I’ve been diving into the security aspects of using &lt;a href=&quot;https://github.com/features/actions&quot;&gt;GitHub Actions&lt;/a&gt; and wanted to share some best practices in one place. If you want to have an overview how and why you need this, you check checkout a session I have on this topic from a user group recording &lt;a href=&quot;/blog/2021/05/28/Solidify-show-Using-GitHub-Actions-Securely&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;From the beginning, GitHub always indicated that the best way to use GitHub Actions is to fork the repository containing them and take control over the code that you are running. That way you can review what the Action is doing before you start using it. You don’t want to run just any random code from the internet, would you?&lt;/p&gt;

&lt;p&gt;This post is part of a &lt;a href=&quot;/blog/2021/02/06/GitHub-Actions&quot;&gt;series&lt;/a&gt; on best practices for using GitHub Actions in a secure way. You can find the other posts here:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2021/02/06/GitHub-Actions-Forking-Repositories&quot;&gt;Forking action repositories&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2021/02/07/GitHub-Actions-Security-Private-Runners&quot;&gt;Private runners&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2021/03/07/GitHub-Actions-one-workflow-per-runner&quot;&gt;One runner, one workflow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div align=&quot;center&quot;&gt;

  &lt;p&gt;&lt;img src=&quot;/images/2021/20210206/anita-jankovic-c1hg-BHe8Uk-unsplash.jpg&quot; alt=&quot;Image of three forks with their shadows on the wall&quot; /&gt;&lt;/p&gt;

&lt;/div&gt;
&lt;h5 id=&quot;photo-by-anita-jankovic-on-unsplash&quot;&gt;&lt;span&gt;Photo by &lt;a href=&quot;https://unsplash.com/@dslr_newb?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Anita Jankovic&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/s/photos/forks?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/span&gt;&lt;/h5&gt;

&lt;h1 id=&quot;always-fork-the-actions-repository-and-use-that&quot;&gt;Always fork the Actions repository and use that!&lt;/h1&gt;
&lt;p&gt;These days, most GitHub Actions demos &lt;strong&gt;only&lt;/strong&gt; show you how to use them, straight from the source repository. That is scary as heck! We should &lt;strong&gt;always&lt;/strong&gt; mention the best practice of forking them!&lt;/p&gt;

&lt;h1 id=&quot;contents&quot;&gt;Contents&lt;/h1&gt;
&lt;p&gt;This post will go over the following ways to add more security when you are using GitHub Actions:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Why should you verify the code the Action is executing?&lt;/li&gt;
  &lt;li&gt;Pinning versions&lt;/li&gt;
  &lt;li&gt;Forking repositories&lt;/li&gt;
  &lt;li&gt;Keeping your forks up to date&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;why-should-you-verify-the-code-the-action-is-executing&quot;&gt;Why should you verify the code the Action is executing?&lt;/h1&gt;
&lt;p&gt;The power of GitHub Actions is that anyone can create a GitHub Action in a public repository for others to use. That also means that there is no real trust between the user that wants to include the Action in their workflow and the maintainer of the Action repository. In the past, there are numerous examples of bad actors taking over respected repositories and changing the code to do something malicious.&lt;/p&gt;

&lt;p&gt;That also means the onus of checking what the Action’s code is doing is up to you. GitHub has some methods of limiting the Actions you or your organization can use, but these have some issues as well. Read more on this below. GitHub’s security guidance for Actions can be found &lt;a href=&quot;https://docs.github.com/en/actions/learn-github-actions/security-hardening-for-github-actions#using-third-party-actions&quot;&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also look at the maintainer of the Action and how many stars, forks and issues the Action has to gauge if the Action is widely used and regularly maintained.&lt;/p&gt;

&lt;h2 id=&quot;only-allow-actions-from-github&quot;&gt;Only allow Actions from GitHub&lt;/h2&gt;
&lt;p&gt;Your first option to limit Actions being used is to use the setting to only allow Actions made by GitHub. That means that you put your trust into GitHub that the maintainers of the Actions repositories have setup sufficient repository security (limiting who can make changes to the Actions) and always verify incoming changes before publishing new versions of the Actions.
&lt;img src=&quot;/images/2021/20210206/20210206_OnlyAllowFromGitHub.png&quot; alt=&quot;Only allow Actions made by GitHub&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;only-allow-actions-from-verified-creators&quot;&gt;Only allow Actions from Verified Creators&lt;/h2&gt;
&lt;p&gt;The second option is to limit Actions from verified creators. Unfortunately there is no process (that I could find) to become a verified creator. GitHub only states that the maintainer is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a partner that GitHub has worked closely with&lt;/code&gt;.
&lt;img src=&quot;/images/2021/20210206/20210206_OnlyAllowFromVerifiedCreators.png&quot; alt=&quot;Only allow Actions made by verified creators&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;only-allow-specific-actions&quot;&gt;Only allow specific Actions&lt;/h2&gt;
&lt;p&gt;The third option you have is to limit Actions to the specific ones you list, either directly with the full path to the Action repository, of by using a minimatch filter that limits the Actions to a specific organization.
&lt;img src=&quot;/images/2021/20210206/20210206_OnlyAllowSpecificActions.png&quot; alt=&quot;Only allow specific Actions&quot; /&gt;&lt;/p&gt;
&lt;h5 id=&quot;note-that-you-can-also-use-specific-commit-shas-here-as-well-more-on-that-later&quot;&gt;Note that you can also use specific commit SHA’s here as well, more on that later&lt;/h5&gt;

&lt;h1 id=&quot;pinning-versions&quot;&gt;Pinning versions&lt;/h1&gt;
&lt;p&gt;After you have checked the Action itself, you can make the decision to start using them. One of the first options you have is to &lt;strong&gt;pin the Action version&lt;/strong&gt;. By adding the minor or major version of the Action.
This will make sure you are always using the same version and (hopefully) prevent any breaking changes from messing up your use of the Action later on.&lt;/p&gt;

&lt;h2 id=&quot;example-pinning-the-version&quot;&gt;Example pinning the version&lt;/h2&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;uses: gaurav-nelson/github-Action-markdown-link-check@v1
uses: gaurav-nelson/github-Action-markdown-link-check@v1.0.1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;In the example, I’m pinning the major version in the first line to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v1&lt;/code&gt; and pinning the minor version to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v1.0.1&lt;/code&gt;. This works as long as the Action author is using &lt;a href=&quot;https://semver.org/&quot;&gt;semantic versioning&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The downside of this method is that there is no way to guarantee that the source code of that version has been altered after you pin the version: an author can create a new release (with new code) and use the same version tag for the release. That means that the code that is running in your pipeline could have been altered, without you knowing about it!&lt;/p&gt;

&lt;h3 id=&quot;safely-pinning-version-by-using-the-commit-sha&quot;&gt;Safely pinning version by using the commit SHA&lt;/h3&gt;
&lt;p&gt;The best practice for pinning Action to the version you have reviewed, is by pinning it using the commit SHA: this value is created for each commit and is immutable: meaning that this value cannot be changed without changing the code and that any change to the code will generate a new commit SHA.&lt;/p&gt;

&lt;h2 id=&quot;example-pinning-the-commit-sha&quot;&gt;Example pinning the commit SHA&lt;/h2&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;uses: gaurav-nelson/github-Action-markdown-link-check@f0656de48f62c1777d073db4a5816eba1dcc1364
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;In the example you see a full length commit SHA that will run for this Action. You can find the commit SHA from the source repository by going to the commit itself with this link:
&lt;img src=&quot;/images/2021/20210206/20210206_FindCommitSHA.png&quot; alt=&quot;Example of finding the commit SHA&quot; /&gt;&lt;/p&gt;
&lt;h5 id=&quot;note-dont-use-the-short-8-character-sha-this-is-much-less-secure-and-will-be-deprecated-as-of-february-the-15th-2021-announcement-always-use-the-full-sha&quot;&gt;Note: don’t use the short 8 character SHA: this is much less secure and will be deprecated as of February the 15th, 2021 (&lt;a href=&quot;https://github.blog/changelog/2021/2021-01-21-github-Actions-short-sha-deprecation/&quot;&gt;announcement&lt;/a&gt;). Always use the full SHA.&lt;/h5&gt;

&lt;h1 id=&quot;forking-repositories&quot;&gt;Forking repositories&lt;/h1&gt;
&lt;p&gt;The &lt;strong&gt;best&lt;/strong&gt; best practice is to completely limit your organization to only use Actions from an organization you control yourself and then fork all Actions to that organization.&lt;/p&gt;

&lt;p&gt;I recommend creating a specific organization that only has Actions repositories in them. That way I have a central location to manage all my Actions and I can limit the Actions that are allowed in any other organization I have.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210206/20210206_OnlyAllowSpecificActions.png&quot; alt=&quot;Only allow specific Actions&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;benefits-of-forking&quot;&gt;Benefits of forking&lt;/h1&gt;
&lt;p&gt;An overview of the benefits of forking the Actions repositories:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;This gives you full control over the Actions you or your organization can use.&lt;/li&gt;
  &lt;li&gt;You have a copy of the Action in case something happens with the maintainer or the repo of the Action (this should not be underestimated).&lt;/li&gt;
  &lt;li&gt;You can review the Action’s code before you start using it in production (meaning your own workflows).&lt;/li&gt;
  &lt;li&gt;You know for sure that the source code of the Action does not change between your own runs.&lt;/li&gt;
  &lt;li&gt;You have full control over updates and when to review and incorporate them or not.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;dont-block-devops-teams&quot;&gt;Don’t block DevOps teams&lt;/h2&gt;
&lt;p&gt;Keep in mind that only allowing Actions from your own organization will possibly block your DevOps teams from finding, testing and then incorporating new Actions in their daily work.&lt;/p&gt;

&lt;p&gt;Follow the DevOps culture and empower teams to review new Actions and have a process to incorporate the new Actions in their normal workflows. This also allows them to take up ownership of the Action by reviewing the Actions source code before forking them to your Action’s organization.&lt;/p&gt;

&lt;p&gt;To unblock them I recommend documenting the process and having a separate organization where they are free to test new Actions. Fork the Action repository there first, test them out and after diligently vetting the Action, fork them again into the production organization allowed in your normal workflows.&lt;/p&gt;

&lt;h1 id=&quot;keeping-your-forks-up-to-date&quot;&gt;Keeping your forks up to date&lt;/h1&gt;
&lt;p&gt;If you are following the &lt;strong&gt;best&lt;/strong&gt; best practice of forking the Actions you want to use, the you find another problem: how do you keep your forked repositories up to date? You can be on the lookout in the GitHub user interface for messages that your fork is a number of commits behind the parent repository, but when (if ever) will you come to that specific page?&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210206/20210206_ForkIsBehindParent.png&quot; alt=&quot;GitHub UI Message that the repository is 2 commits behind the parent repository&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Looking around I could not find a great way of keeping all Actions in your organization up to date AND giving you an option to review the incoming changes before you start using them, so I created my own 😁.&lt;/p&gt;

&lt;h2 id=&quot;keeping-your-forked-github-actions-repositories-up-to-date&quot;&gt;Keeping your forked (GitHub Actions) repositories up to date&lt;/h2&gt;
&lt;p&gt;I wanted a process that would update my forked repositories with these requirements:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;work for &lt;strong&gt;all&lt;/strong&gt; forked repositories in my organization (so not just for one repo at a time)&lt;/li&gt;
  &lt;li&gt;allow me to review the incoming changes&lt;/li&gt;
  &lt;li&gt;update the forked repository with minimal manual intervention&lt;/li&gt;
  &lt;li&gt;enable others to use this method as easy as possible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this purpose I created this repository: &lt;a href=&quot;https://github.com/rajbos/github-fork-updater&quot;&gt;github.com/rajbos/github-fork-updater&lt;/a&gt;. The information to start using it can be found in the readme and I will list them here as well:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Fork the repo into your Actions organization.&lt;/li&gt;
  &lt;li&gt;Enable issues (these are off by default for forks)&lt;/li&gt;
  &lt;li&gt;Enable the workflows in the fork and enable the schedule on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;check-workflow&lt;/code&gt; (off for security reasons)&lt;/li&gt;
  &lt;li&gt;Trigger the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;check-workflow&lt;/code&gt; manually to get going (or wait for the schedule to be triggered, which is on workdays at 07:00)&lt;/li&gt;
  &lt;li&gt;Check any new issues being created&lt;/li&gt;
  &lt;li&gt;Add a secret to the forked repository with the name &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PAT_GITHUB&lt;/code&gt; and the rights to push changes to the repositories in your organization&lt;/li&gt;
  &lt;li&gt;Review the incoming changes for the fork you want to update&lt;/li&gt;
  &lt;li&gt;Label the issue with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;update-fork&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Wait for the magic to happen&lt;/li&gt;
  &lt;li&gt;Your fork is updated&lt;/li&gt;
  &lt;li&gt;The issue is closed&lt;/li&gt;
  &lt;li&gt;Sit back and wait for new notifications the next time the fork is out of date&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also watch the demo video I created here:
  &lt;a href=&quot;https://www.youtube.com/watch?v=Jj033ffS1YQE&quot;&gt;&lt;img src=&quot;https://img.youtube.com/vi/Jj033ffS1YQ/hqdefault.jpg&quot; alt=&quot;GitHub Fork Updater&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;This will make your life a lot easier. The default GitHub &lt;a href=&quot;https://docs.github.com/en/github/managing-subscriptions-and-notifications-on-github/about-notifications&quot;&gt;notification methods&lt;/a&gt; are used to notify you of new issues. All issues are always in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GitHub Fork Updater&lt;/code&gt; repository, instead of all over the place. And you can choose when to review the incoming changes and have an easy way to update the fork.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>GitHub Actions &amp; Security - NDC London</title>
			<link href="https://devopsjournal.io/blog/2021/01/28/GitHub-Actions-NDC-London"/>
			<updated>2021-01-28T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2021/01/28/GitHub-Actions-NDC-London</id>
			<content type="html">&lt;p&gt;Today I delivered my GitHub Actions &amp;amp; Security session at &lt;a href=&quot;https://ndc-london.com/agenda/how-to-secure-your-github-actions-0vd4/0gnrspzjdmb&quot;&gt;NDC London&lt;/a&gt; for the first time (both for the session and NDC London 😁).&lt;/p&gt;

&lt;p&gt;NDC London was a lot of fun! The organizers really went out of their way to enable speakers to even see the attendees, welcome you into the room and guide you through things.&lt;/p&gt;

&lt;h1 id=&quot;presentation&quot;&gt;Presentation&lt;/h1&gt;
&lt;p&gt;You can find the presentation on &lt;a href=&quot;https://www.slideshare.net/RobBos10/github-actions-security&quot;&gt;SlideShare&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Keep note of this page, as I will start adding new blogpost discussion security measures when using GitHub Actions here with the learnings from this session.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210128/20210128_01_SlideShare.png&quot; alt=&quot;SlideShare image of the presentation&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;how-to-secure-your-github-actions&quot;&gt;How to secure your GitHub Actions&lt;/h1&gt;

&lt;p&gt;When working in the real world with continuous integration / continuous deployment, you have to take care of your pipelines. - Who can push to an environment? - Who could change the connection strings to the database? - Who can create new resources in your cloud environment? - Do you trust your third party extensions? I’ll go over each of these aspects of your GitHub Actions Workflows and show you what to look for and how to improve your security stance without locking every DevOps engineer out.&lt;/p&gt;

&lt;p&gt;More info about this session can be found &lt;a href=&quot;https://sessionize.com/s/RobBos/how_to_secure_your_github_actions/36976&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>GitHub Container Registry</title>
			<link href="https://devopsjournal.io/blog/2021/01/06/GitHub-Container-Registry"/>
			<updated>2021-01-06T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2021/01/06/GitHub-Container-Registry</id>
			<content type="html">&lt;p&gt;I wanted to use the &lt;a href=&quot;https://docs.github.com/en/free-pro-team@latest/packages/guides/about-github-container-registry&quot;&gt;GitHub Container Registry&lt;/a&gt; to host an image for me and had some issues setting things up. To save me some time the next time I need this, and hopefully for someone else as well, I wanted to document how this process works.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210106/evgeni-tcherkasski-SHA85I0G8K4-unsplash.jpg&quot; alt=&quot;Image of a lighthouse at night&quot; /&gt;&lt;/p&gt;
&lt;h5 id=&quot;photo-by-evgeni-tcherkasski-on-unsplash&quot;&gt;&lt;span&gt;Photo by &lt;a href=&quot;https://unsplash.com/@evgenit?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Evgeni Tcherkasski&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/s/photos/lighthouse?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/span&gt;&lt;/h5&gt;

&lt;h2 id=&quot;beta-period&quot;&gt;Beta period&lt;/h2&gt;
&lt;p&gt;During the beta, the container registry will be free to use. Open-source and public repositories are always entirely free to use, but private repositories will fall under the &lt;a href=&quot;https://docs.github.com/en/free-pro-team@latest/github/setting-up-and-managing-billing-and-payments-on-github/about-billing-for-github-packages&quot;&gt;standard billing rates&lt;/a&gt; for GitHub Packages after the beta is over. The free tier of that includes 500 MB of storage and 1 GB of transfer every month.&lt;/p&gt;

&lt;h2 id=&quot;enable-github-container-registry&quot;&gt;Enable GitHub Container Registry&lt;/h2&gt;
&lt;p&gt;Currently, the registry is in Beta, so you’ll need to enable the beta feature on your profile or on the organization level you want to use it on. To do so, go to your profile (or organization) and go to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Feature preview&lt;/code&gt; where you can toggle the feature. You’ll notice a new ‘Packages’ tab on you profile page as well.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210106/20210106_01_EnablePackages.png&quot; alt=&quot;Location to find the preview settings&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;preparing-to-push-image-to-the-registry&quot;&gt;Preparing to push image to the registry&lt;/h2&gt;
&lt;p&gt;Currently the only way to authenticate with GitHub Container Registry is to use a GitHub &lt;a href=&quot;https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token&quot;&gt;Personal Access Token (PAT)&lt;/a&gt;. GitHub already knows this is an issue because the PAT can be used in the entire account it is created and will change that later.
For now the advisory is to create a specific PAT with only rights to the registry and use that.&lt;/p&gt;

&lt;p&gt;These are the scopes you need to enable for the PAT:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;read:packages scope to download container images and read their metadata.&lt;/li&gt;
  &lt;li&gt;write:packages scope to download and upload container images and read and write their metadata.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to delete the packages, also use this scope:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;delete:packages scope to delete container images.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210106/20210106_02_PAT.png&quot; alt=&quot;Personal Access Token Creation&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;using-a-github-workflow-to-build-and-push-an-image&quot;&gt;Using a GitHub workflow to build and push an image&lt;/h1&gt;
&lt;p&gt;To push a new image from a workflow, use the complete example below.&lt;/p&gt;

&lt;p&gt;The steps used are as follows:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Get the source code with the docker file and anything you need to build the image.
``` yaml
    &lt;ul&gt;
      &lt;li&gt;uses: actions/checkout@v1
```&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Build the image
``` yaml
    &lt;ul&gt;
      &lt;li&gt;name: Build the Docker image
run: docker build -t ghcr.io/«ACCOUNT NAME»/«IMAGE NAME»:«VERSION» .
```&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;The normal &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker build&lt;/code&gt; step where I am tagging the image with the tag I want to push to the registry
``` yaml
    &lt;ul&gt;
      &lt;li&gt;name: Build the Docker image
run: docker build -t ghcr.io/«ACCOUNT NAME»/«IMAGE NAME»:«VERSION» .
```&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Authenticate with the GitHub Container Registry
Using the recommended setup from &lt;a href=&quot;https://docs.github.com/en/packages/guides/pushing-and-pulling-docker-images#authenticating-to-github-container-registry&quot;&gt;GitHub&lt;/a&gt;) for safety
``` yaml&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;name: Setup GitHub Container Registry
run: echo “${{ secrets.GH_PAT }}” | docker login https://ghcr.io -u ${{ github.actor }} –password-stdin&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
5. The normal `docker push` step to push the container
``` yaml
    - name: push to GitHub Container Registry
      run:  docker push ghcr.io/&amp;lt;&amp;lt;ACCOUNT NAME&amp;gt;&amp;gt;/&amp;lt;&amp;lt;IMAGE NAME&amp;gt;&amp;gt;:&amp;lt;&amp;lt;VERSION&amp;gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;full-workflow-example&quot;&gt;Full workflow example&lt;/h2&gt;
&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Build and Push Docker container&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;branches&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;main&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;build-and-push&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v1&lt;/span&gt;

    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Build the Docker image&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;docker build -t ghcr.io/&amp;lt;&amp;lt;ACCOUNT NAME&amp;gt;&amp;gt;/&amp;lt;&amp;lt;IMAGE NAME&amp;gt;&amp;gt;:&amp;lt;&amp;lt;VERSION&amp;gt;&amp;gt; .&lt;/span&gt;

    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Setup GitHub Container Registry&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;echo &quot;$&quot; | docker login https://ghcr.io -u $ --password-stdin&lt;/span&gt;

    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;push to GitHub Container Registry&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;  &lt;span class=&quot;s&quot;&gt;docker push ghcr.io/&amp;lt;&amp;lt;ACCOUNT NAME&amp;gt;&amp;gt;/&amp;lt;&amp;lt;IMAGE NAME&amp;gt;&amp;gt;:&amp;lt;&amp;lt;VERSION&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;secret-names&quot;&gt;Secret names&lt;/h2&gt;
&lt;p&gt;Do note that I am using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;secrets.GH_PAT&lt;/code&gt; to inject the PAT token I’m using into the workflow. You cannot use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GITHUB&lt;/code&gt; as a prefix for the secret name, so you need to change that. The secrets user interface doesn’t tell you about that in a great way, which I have sent GitHub feedback on through the &lt;a href=&quot;https://github.community/t/add-a-warning-or-explanation-when-saving-a-secret-with-a-wrong-name/154018&quot;&gt;GitHub Community&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id=&quot;consuming-the-new-image&quot;&gt;Consuming the new image&lt;/h1&gt;
&lt;p&gt;By default the images are kept behind a login, so if you want to make the image publicly available you need to do that for each package.&lt;/p&gt;

&lt;h2 id=&quot;keep-the-image-behind-a-login&quot;&gt;Keep the image behind a login&lt;/h2&gt;
&lt;p&gt;To use the image behind the login, you’ll need to authenticate with the registry first:&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;echo&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;GH_PAT&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;docker&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;login&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;https://ghcr.io&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;USERNAME&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--password-stdin&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;enable-the-public-registry&quot;&gt;Enable the public registry&lt;/h2&gt;
&lt;h4 id=&quot;note-this-is-a-one-way-trip-it-cannot-be-made-private-after-making-it-publicly-available&quot;&gt;Note: this is a one way trip: it cannot be made private after making it publicly available&lt;/h4&gt;

&lt;p&gt;To change this setting: go to the package and to its settings:
&lt;img src=&quot;/images/2021/20210106/20210106_03_MakePackagePublic.png&quot; alt=&quot;Package settings&quot; /&gt;&lt;/p&gt;

&lt;p&gt;And make the image publicly available:
&lt;img src=&quot;/images/2021/20210106/20210106_04_MakePackagePublic.png&quot; alt=&quot;Make the image public&quot; /&gt;&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>DevOps Maturity Levels - Continuous Deployment</title>
			<link href="https://devopsjournal.io/blog/2020/12/31/DevOps-Continuous-Deployment"/>
			<updated>2020-12-31T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2020/12/31/DevOps-Continuous-Deployment</id>
			<content type="html">&lt;p&gt;If you have a good &lt;a href=&quot;/blog/2020/12/31/DevOps-Continuous-Integration&quot;&gt;Continuous Integration&lt;/a&gt; process in place, you can start using the generated artefact to deploy that to an environment as the next &lt;a href=&quot;/blog/2020/12/31/DevOps-Maturity-Levels&quot;&gt;state of enlightenment&lt;/a&gt; in your DevOps way of working. Check that link for posts on the other topics.&lt;/p&gt;

&lt;p&gt;Note: in this case I specifically mention an &lt;strong&gt;environment&lt;/strong&gt;: any place you can roll out your artefact is part of your Continuous Deployment strategy. Where specifically is part of forming your strategy and there are plentiful ways of doing so. Different strategies will be discussed further down in this post. For now, lets take rolling your application out onto a test environment as a starting point: the goal is to roll out the application so you can verify if things are working as expected. You typically do so on a test environment: something that is not production 😄.&lt;/p&gt;

&lt;h4 id=&quot;note-application-can-be-anything-but-ill-use-a-web-application-as-an-example-here&quot;&gt;Note: Application can be anything, but I’ll use a web-application as an example here&lt;/h4&gt;
&lt;h4 id=&quot;note-environment-in-this-case-will-then-be-a-hosting-environment-to-run-the-application-so-something-of-a-webserver-in-this-example&quot;&gt;Note: Environment in this case will then be a hosting environment to run the application, so something of a webserver in this example&lt;/h4&gt;
&lt;h4 id=&quot;note-ill-use-the-application-as-running-in-the-cloud-as-an-example-some-points-can-be-made-about-other-platforms&quot;&gt;Note: I’ll use the application as running in the cloud as an example, some points can be made about other platforms&lt;/h4&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20201231/20201231_01_StatesOfEnlightenment.png&quot; alt=&quot;Displaying the different States of Enlightenment&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;continuous-deployment-cd&quot;&gt;Continuous Deployment (CD)&lt;/h1&gt;
&lt;p&gt;There are some differentiation one can make about a difference between Continuous Delivery (deliver the artefact to a server) and Continuous Deployment (enabling the changes to an end-user). I’m not making that distinction in this post: the goal is to roll out a new version of the application to our test environment. As far as DevOps enlightenment goes I think the following steps can be separated out:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021/20210103/20210103_01_ContinuousDeployment.png&quot; alt=&quot;Stages of Continuous Deployment flow&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;deploy-to-an-existing-environment&quot;&gt;Deploy to an existing environment&lt;/h2&gt;
&lt;p&gt;When you start here, you’ll probably start with an existing environment. Perhaps you are already using a tool to deploy to that environment manually: time to step up your game and deliver the updates automatically. With DevOps tooling you can automate this process to deploy the update with each new artefact being created: you want to automatically trigger the start of your deployment when this happens and the CD pipeline should do the rest. In the cloud example you’d use these steps:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Trigger the CD pipeline on a new artefact&lt;/li&gt;
  &lt;li&gt;Download the artefact&lt;/li&gt;
  &lt;li&gt;Connect to the hosting environment&lt;/li&gt;
  &lt;li&gt;Deploy to the environment&lt;/li&gt;
  &lt;li&gt;Signal the development team a deployment was successfully done&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;infrastructure-as-code-iac&quot;&gt;Infrastructure as Code (IaC)&lt;/h2&gt;
&lt;p&gt;A next step could be to start creating the environment you want to deploy to automatically as well. This enables you to:&lt;/p&gt;
&lt;ol type=&quot;a&quot;&gt;
    &lt;li&gt;create a clean testing environment whenever you need, for example for a Pull Request verification build&lt;/li&gt;
    &lt;li&gt;recover from issues with you environment for example when somebody accidentally deletes something&lt;/li&gt;
    &lt;li&gt;rollout the environment in different cloud regions: when automating the creation of the environment this becomes very easy to do&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This step does not necessarily come in this order and could be skipped in favor of the following two steps. You’ll probably find the need to start working with IaC soon thereafter, and adding it here usually saves you pain later on.&lt;/p&gt;

&lt;h2 id=&quot;nothing-broke-with-the-deployment-checks&quot;&gt;Nothing broke with the deployment checks&lt;/h2&gt;
&lt;p&gt;After rolling new version out automatically, it is good to add a step in the CD pipeline to verify that the application still is working as expected. You don’t want to rollout every change and find out from your users (or stakeholders!) that the test environment is not working! Better to add a step and verify yourself.&lt;/p&gt;

&lt;p&gt;At first it could be a basic test: if I navigate to the web-application, do I get a HTTP 200 Ok response back:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;url=&quot;https://devopsjournal.io&quot;
status=`curl --silent --head $url | head -1 | cut -f 2 -d&apos; &apos;`

if [ &quot;$status&quot; != &quot;200&quot; ]
then
    echo &quot;status was other than &apos;200&apos;: was &apos;$status&apos;&quot;
    exit 1
fi
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This should at least tell you if your application can be loaded by the hosting environment and can load the start page. It doesn’t really tell you anything else.&lt;/p&gt;

&lt;p&gt;A next step could be to add the additional tests you need to get more confidence in your application that indicate that it still is working as expected. You could go all out with this and start creating end-to-end tests for the application checking each and every screen and functionality. Unfortunately these tests are hard to build, maintain and slow to execute.&lt;/p&gt;

&lt;p&gt;You can also add just the tests you need to verify that the basics are there: test the index page, maybe login with a test user and check if some mayor parts of the application can be loaded. Then you already have several end-to-end integration points you are testing as part of your deployment: the application itself, some user authentication and perhaps authorization and maybe even the way you build up your pages or a menu for example. Try to make sure you can run these test fast and in parallel to gain even more speed.&lt;/p&gt;

&lt;h2 id=&quot;automatic-rollback-options&quot;&gt;Automatic Rollback options&lt;/h2&gt;
&lt;p&gt;What do you want to happen when you detect that a new deployment fails? In DevOps we usually say we want to roll forward: find the issue, fix it and roll the new out to the environments. When starting to do Continuous Deployment, this is not always the first thing you think about. Perhaps rolling out doesn’t give you enough confidence yet. Or perhaps your rollout is not fast enough for this.
And there is always the case where you want to investigate what is going wrong at all, before you can fix it. Finding the issue can take some time. In the meanwhile, you want to get back to a known state. This can be hard, depending on the way you rollout updates. For example, think of database upgrades here: if you are changing data in the update, perhaps the previous version of the application cannot handle the new database schema. There are ways to remediate that, giving you the opportunity to rollback: re-deploy the last known good version, enabling you to find the issue with the erroneous version.&lt;/p&gt;

&lt;p&gt;Often, the systems you use for your CD pipeline has an option for rolling back to a previous version: this can either be triggered manually or automatically.&lt;/p&gt;

&lt;h2 id=&quot;canary-deployments&quot;&gt;Canary deployments&lt;/h2&gt;
&lt;p&gt;Alright, when you have your setup so far that you can deploy to any environment, with confidence that the new version is still working and with a rollback option if needed, you can start thinking about Canary Deployments: a deployment to a subset of your users. This is a method to limit the scope of the changes to only a small part of your users in an effort to limit the possible issues that the new version has. This way you can monitor the changes and from that decide if the changes are behaving good enough to roll them out to a larger set of your users.&lt;/p&gt;

&lt;p&gt;There are lots of ways to roll out to a subset of your users: you can use a percentage of the incoming requests and direct them to a new server and slowly ramp that up, or let the users set a setting that they want to be alpha/beta users (even for just one functionality). This enables you to monitor the environment and roll out with more confidence.&lt;/p&gt;

&lt;h2 id=&quot;deploy-without-downtime&quot;&gt;Deploy without downtime&lt;/h2&gt;
&lt;p&gt;Most deployments to a server involve some downtime. Overwriting files on a web server has issues with locks when files are in use. Some servers make your stop the running website, overwrite the files and then start serving incoming request again.&lt;/p&gt;

&lt;p&gt;This stage goes well with the previous one: if you have the ability to get an environment and split the traffic up between the old and the new version, you have found the way to deploy without any downtime: the basis is that both the old and new version are running at the same time and you control where the request are going to.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>DevOps Maturity Levels - Source control</title>
			<link href="https://devopsjournal.io/blog/2020/12/31/DevOps-Source-Control"/>
			<updated>2020-12-31T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2020/12/31/DevOps-Source-Control</id>
			<content type="html">&lt;p&gt;One of the first things to get in order when improving your &lt;a href=&quot;/blog/2020/12/31/DevOps-Maturity-Levels&quot;&gt;DevOps way of working&lt;/a&gt; is having proper version control of your source code. Source code in this case means anything: from application source code that you can build and deploy, to scripts you use to do the deployment. In my opinion: anything around your team that can be saved as text, should end up in source control.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20201231/20201231_01_StatesOfEnlightenment.png&quot; alt=&quot;Displaying the different States of Enlightenment&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;source-control-in-order&quot;&gt;Source Control in order&lt;/h1&gt;
&lt;p&gt;I still come across teams that don’t have any source control for some parts of their tools, or have ancient systems that don’t have support anymore. Or even worse: things live on a specific server, stuffed away beneath someone’s desk 😱. If that ‘server’ fails, everything could be gone. If a copy is stored on someone else’s machine, then maybe getting things back in working order is ‘just’ a few days of diligent work.&lt;/p&gt;

&lt;p&gt;At a previous company something like this happened on Christmas Eve and I was very lucky to only find out about this &lt;em&gt;after&lt;/em&gt; the holiday period, since someone else had taken up the chore to get a new server (a remarkable feat around the holidays!) AND restore everything back to working order for everyone in the new year.&lt;/p&gt;

&lt;h2 id=&quot;git-distributed-version-control&quot;&gt;Git: distributed version control&lt;/h2&gt;
&lt;p&gt;For source control you can pick any tool you like, I’d only recommend it to be Git based and skip the other ones (like SVN, TFVC). Git is the de-facto standard these days: investing in learning it will give you a solid base that can be reused across different organizations.&lt;/p&gt;

&lt;p&gt;One of the reasons Git has become the standard is that it is distributed version control: this means everyone that is using it, has the entire code history on their system (up until the last time they loaded the last changes): you get a local copy to work with. So in the case something happens with the system you labeled ‘server’: all developers will have a local copy as well. Getting back to a working environment can then be super easy.&lt;/p&gt;

&lt;p&gt;Common offerings for Git (that I have worked with) are GitHub / GitLab / Azure DevOps. Each of these has on premises offerings (meaning you install them in your own network) or hosted versions where the hosting is handled for you. I’d recommend getting a hosted version when using it in production, since those companies can host it a lot better then you can yourself 😁.&lt;/p&gt;

&lt;h2 id=&quot;git-getting-started&quot;&gt;Git: Getting started&lt;/h2&gt;
&lt;p&gt;Looking around online gives you a lot of options to get started with Git. You can find the tools and documentations on the &lt;a href=&quot;https://git-scm.com/&quot;&gt;Git Source Control Management&lt;/a&gt; website where they even host introduction video’s to get you &lt;a href=&quot;https://git-scm.com/video/what-is-version-control&quot;&gt;started&lt;/a&gt; or use GitHub’s &lt;a href=&quot;https://try.github.io/&quot;&gt;training&lt;/a&gt; environment to get started with a hosted solution for free (as long as your code is made available publicly).&lt;/p&gt;

&lt;p&gt;After getting to know how you can work with Git and push changes to a central location, do make sure your team knows about using branches and start using &lt;a href=&quot;https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow&quot;&gt;feature branches&lt;/a&gt; to work in isolation.&lt;/p&gt;

&lt;p&gt;Want to know more? Reach out to me with any question you have.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>DevOps Maturity Levels</title>
			<link href="https://devopsjournal.io/blog/2020/12/31/DevOps-Maturity-Levels"/>
			<updated>2020-12-31T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2020/12/31/DevOps-Maturity-Levels</id>
			<content type="html">&lt;p&gt;I was thinking about the teams I’ve been helping out in my professional work life and suddenly noticed that there seem to be different stages that each team goes through in an effort to improve something in their day-to-day work. Usually I start at a new assignment with a specific question and together we evolve my assignment from there.&lt;/p&gt;

&lt;p&gt;The entry point often is something specific, like for example:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Migrate our on premise source control to something in the cloud (usually much newer)&lt;/li&gt;
  &lt;li&gt;Quality assessments of the code base&lt;/li&gt;
  &lt;li&gt;Way of working to get changes into production&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After the initial question is handled (or planned to be handled), I often start helping the teams with additional steps they can include to improve certain aspects of their way of working and we go from there.&lt;/p&gt;

&lt;p&gt;Since my expertise is DevOps, all these things are around those topics. I’ve done a lot of things around the DevOps cycle, from explaining Git as a way to version your code to monitoring in production and deployment into a cloud environment.
Note that these items are mostly based on using technology to improve things. The world of people and culture in an organization is for another time 😄.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20201231/20201231_01_DevOpsCycle.png&quot; alt=&quot;DevOps Cycle&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;devops---states-of-enlightenment&quot;&gt;DevOps - States of enlightenment&lt;/h1&gt;
&lt;p&gt;Looking back at my assignments, I found different states where the teams where in their DevOps way of working and from that you can find the next thing that would probably help to improve their environment. With environment I mean everything they do to keep the application running in production. Of course, some teams already have (part of) these topics handled, so you can check other states as well to find possible improvements.&lt;/p&gt;

&lt;p&gt;I’ve tried to set these in a logical order in the image below and will (try to) create a blog post for each stage to describe what this means and ways to implement this in your setup. You can always dive deeper into a specific item no matter where your team is in the improvement process or skip things and come back later. In my mind, this is an order that makes sense to me:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20201231/20201231_01_StatesOfEnlightenment.png&quot; alt=&quot;Displaying the different States of Enlightenment&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I’ve color coded the states indicating their main point of view and if this is Developer driven (blue) or Operations driven (green), although this is not always as clear cut of course.&lt;/p&gt;

&lt;h2 id=&quot;dev---source-control-in-order&quot;&gt;Dev - Source control in order&lt;/h2&gt;
&lt;p&gt;From a developer perspective you need to have your source control in order. I still come across code that is not version controlled at all 🙀, sometimes it just sits on a user machine, a share or even worse: on the shared server used for CI/CD (continuous integration and deployment)!
Link 👉 &lt;a href=&quot;/blog/2020/12/31/DevOps-Source-Control&quot;&gt;DevOps - Source control in order&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;dev---ci-continuous-integration-pipelines&quot;&gt;Dev - CI (continuous integration) pipelines&lt;/h2&gt;
&lt;p&gt;From a developer driven wish they usually start implementing something of a continuous pipeline, that at least builds the code on a different machine then the developer who wrote that code. This state will be split up into different maturity levels since there are a lot of different improvements to be made.
Link 👉 &lt;a href=&quot;/blog/2020/12/31/DevOps-Continuous-Integration&quot;&gt;DevOps - Continuous Integration&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;ops---cd-continuous-deployment--delivery-pipelines&quot;&gt;Ops - CD (continuous deployment / delivery) pipelines&lt;/h2&gt;
&lt;p&gt;Often times the people who are deploying the application to an environment have the need to automate these actions into a repeatable process that can be executed without manual interactions. I’ve still seen teams that where orchestrating this process with six designated team members, each having to wait for someone else was done to start the next step in the deployment by running their own batch file (non versioned of course!) with custom settings for that environment. This state also can be split up into multiple maturity levels.
Link 👉 &lt;a href=&quot;/blog/2021/01/03/DevOps-Continuous-Deployment&quot;&gt;DevOps - Continuous Deployment&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;devops---pre-and-post-deployment-gates-with-actual-checks&quot;&gt;DevOps - Pre and post deployment gates with actual checks&lt;/h2&gt;
&lt;p&gt;This is the first common scenario where both dev and ops have a stake to get things done: operations usually has an interest in checking if things are still working after a deployment and this often is the last step for the developers when they thing their work is complete.&lt;/p&gt;

&lt;h2 id=&quot;ops---beginning-with-monitoring&quot;&gt;Ops - Beginning with monitoring&lt;/h2&gt;
&lt;p&gt;Heavily ops driven of course, although (finally) devs are more and more looking at the actual running application to see how things are going. To get more insights, monitoring is an important topic to handle. When devs start to tackle this, they often take a different starting point then someone with an operations background.&lt;/p&gt;

&lt;h2 id=&quot;ops---alerting&quot;&gt;Ops - Alerting&lt;/h2&gt;
&lt;p&gt;When monitoring is setup, it would really help a lot if you also get alerts from your monitoring setup. Often you start here and you get &lt;strong&gt;too many&lt;/strong&gt; alerts. Getting this right is hard, yet very important. From alerting you can also start with a good recovery mechanism, like adding playbooks/runbooks to start a recovery procedure (this could even be scaling things up/out).&lt;/p&gt;

&lt;h2 id=&quot;ops---observability&quot;&gt;Ops - Observability&lt;/h2&gt;
&lt;p&gt;Often triggered by something from monitoring (hence ops), the need arises to have actual insights into what the application is actually doing. Developers usually have the insight in where you can add additional logging in the application to get that information out, hence the color gradient in the image: it starts with ops (often) and then becomes a joint effort to get the information out.&lt;/p&gt;

&lt;h2 id=&quot;devops---feature-flags&quot;&gt;DevOps - Feature flags&lt;/h2&gt;
&lt;p&gt;After having more information about the application through basic logging and observability, you can get on the way with proper feature flags: toggling a flag to enable/disable something in the application/environment to for example enable new features, or disable a recommendation engine on black Friday to have a more reliable application. This is often a joint effort between dev and ops minded people.&lt;/p&gt;

&lt;h2 id=&quot;devops---rapid-ab-testing-with-user-cohorts&quot;&gt;DevOps - Rapid A/B testing with user cohorts&lt;/h2&gt;
&lt;p&gt;When you have feature flags in your system, often you then have another light bulb go off: you can start using these to test in production! Why not give certain users a label like ‘internal’, ‘beta-tester’, ‘customer’ and have only those groups test new features? This enables canary releases (a subset of users get the new feature) or even actual A/B testing to see which application flow gets more customers to buy a product.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>DevOps Maturity Levels - Continuous Integration</title>
			<link href="https://devopsjournal.io/blog/2020/12/31/DevOps-Continuous-Integration"/>
			<updated>2020-12-31T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2020/12/31/DevOps-Continuous-Integration</id>
			<content type="html">&lt;p&gt;If you have proper source control in place, you have a central location that holds the source code. From that location you can start with Continuous Integration as a next &lt;a href=&quot;/blog/2020/12/31/DevOps-Maturity-Levels&quot;&gt;state of enlightenment&lt;/a&gt; in your DevOps way of working. Check that link for posts on the other topics.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20201231/20201231_01_StatesOfEnlightenment.png&quot; alt=&quot;Displaying the different States of Enlightenment&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;continuous-integration-ci&quot;&gt;Continuous Integration (CI)&lt;/h1&gt;
&lt;p&gt;With continuous integration we mean doing something with each incoming change to the central source code location that verifies that things still ‘work’ as intended. For an application, this could mean building the source code and verify that you have everything you need to build it. This prevents ‘it works on my machine’ that you hear in teams that don’t have a central build environment to do these checks. Finding out what is causing an issue between a deployment and your local developer environment can be hard that way.&lt;/p&gt;

&lt;p&gt;By having a central location (or server) that starts doing things to verify code changes you prevent a lot of issues: you make sure all changes needed to create a ‘build’ are checked in and that the code is not just working because it could be build on a developers machine.&lt;/p&gt;

&lt;p&gt;There are multiple aspects of Continuous Integration that can be viewed as different maturity levels for a teams way of working. For me, the logical process to add them is listed below:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20201231/20201231_02_ContinuousIntegration.png&quot; alt=&quot;Displaying the different stages to improve your continuous integration process&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;create-a-build-on-changes&quot;&gt;Create a build on changes&lt;/h2&gt;
&lt;p&gt;The first thing to create when improving your DevOps way of working is to create a CI build: the idea of a build is to create an artefact: this can be a zip file that can be deployed onto a webserver for example.&lt;/p&gt;

&lt;p&gt;By automating the steps to create the build you are already moving a way from the ‘works on my machine’ syndrome some developers have 😀. Setting the CI build up on a server designated specifically for builds will make sure that any changes made to the source code can actually be build.&lt;/p&gt;

&lt;p&gt;At the end of the build, the results are stored on the build server (or uploaded to the cloud, or a release location) and labeled with a version number (usually the date with a incremental number for that date or by using date + time).&lt;/p&gt;

&lt;p&gt;We call this process a build pipeline: a set of tasks that can be run to execute the steps to verify the build.&lt;/p&gt;

&lt;h3 id=&quot;only-deploy-build-artifacts&quot;&gt;Only deploy build artifacts&lt;/h3&gt;
&lt;p&gt;The next step is to agree with you team that the only code that is being deployed is coming from the build server as artifacts! No more copy and pasting files from a developers machine. (yes, I’ve seen teams doing that, with all the consequences of not having reliable deployments since you could not find with what code that file was created).&lt;/p&gt;

&lt;h3 id=&quot;build-for-any-change&quot;&gt;Build for any change&lt;/h3&gt;
&lt;p&gt;Make sure you run the build on any change that comes in! You can have different builds: one for verification (the code can be build) and one for creating the actual artefact to be deployed.&lt;/p&gt;

&lt;p&gt;If you are using &lt;a href=&quot;https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow&quot;&gt;feature branches&lt;/a&gt;, build the incoming changes on those branches as well as on the main branch of your code. Of course, you can setup the artefact creation to only run on the main branch, to prevent someone from deploying an artefact from a feature branch. Later on, you could create a new test environment based on a feature branch, to further verify the changes, but we are not there yet.&lt;/p&gt;

&lt;h2 id=&quot;automatic-testing&quot;&gt;Automatic testing&lt;/h2&gt;
&lt;p&gt;When there is a build process in place, you can start adding extra tasks to it in an attempt to &lt;a href=&quot;https://www.youtube.com/watch?v=E4UD1dloNM8&amp;amp;list=PLXVVwOM8uv2wQyhQ7mB_Nv_iXyMuXf-GT&amp;amp;index=2&amp;amp;t=2s&quot;&gt;shift left&lt;/a&gt; (the process of finding bugs earlier in the CI/CD cycle). You want to have more and more confidence that your code is still working as expecting. To verify that, there are multiple ways of doing so.&lt;/p&gt;

&lt;p&gt;One of the ways is including tests that can run in the build pipeline that will verify the code. We group these tests under the name ‘test automation’. There are multiple layers of test that you can run:
|Name|Characteristics|Description|
|—|—|—|
|Unit testing|Fast and cheap|Testing at the lowest level of an application, for example if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Add(a+b)&lt;/code&gt; works with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a=1 and b=-1&lt;/code&gt;|
|Integration testing|Slower and less cheap|Testing points in the application where layers of abstraction meet, for example at the controller level in an MVC application|
|End to end testing|Slowest and most expensive|Executing the application like a user would: by click through the functionality|
|Manual testing|Hard to automate|Often based on a feeling if something works|&lt;/p&gt;

&lt;h2 id=&quot;dependency-checks&quot;&gt;Dependency checks&lt;/h2&gt;
&lt;p&gt;An important check to add to your CI pipeline is to check your dependencies for issues. Looking at a modern application, you learn that &lt;a href=&quot;https://jessehouwing.net/99-percent-of-code-isnt-yours/&quot;&gt;99% of code isn’t yours&lt;/a&gt;. We are using dependencies everywhere because someone else has already solved a problem for us, and we don’t want to reinvent the wheel: we need to focus on adding value to our product.&lt;/p&gt;

&lt;p&gt;The thing is: there are so many dependencies it is hard to keep them up to date. And why would you if they are working as they currently are? Well, sometimes issues are found with the version you are using. Something could have happened to the dependency where a vulnerability was found, or maybe even misused and someone injected additional software in the dependency.&lt;/p&gt;

&lt;p&gt;This is where dependency scanning comes in. There are a lot of different options and offerings around. GitHub has &lt;a href=&quot;https://github.blog/2020/2020-06-01-keep-all-your-packages-up-to-date-with-dependabot/&quot;&gt;Dependabot&lt;/a&gt; that scans your code for versions of the dependency and it’s known issues against the &lt;a href=&quot;https://cve.mitre.org/&quot;&gt;CVE database&lt;/a&gt; (Common Vulnerability and Exposures). There are container scanners that offer the same for your container dependencies.
You can include &lt;a href=&quot;https://www.whitesourcesoftware.com/&quot;&gt;WhiteSource&lt;/a&gt;, &lt;a href=&quot;https://www.blackducksoftware.com/&quot;&gt;BlackDuck&lt;/a&gt; and other tools in your pipeline to at least check the dependencies for know issues.&lt;/p&gt;

&lt;p&gt;Most of the offerings also include a license check against the dependency. Depending on the application you are working on, a license like the &lt;a href=&quot;https://www.gnu.org/licenses/gpl-3.0.en.html&quot;&gt;GPL&lt;/a&gt; might be a big (legal) issue in your organization to use. You really want to include a step in your CI pipeline that checks those items and fails the build when a dependency is used with a license you don’t want to support in your organization.&lt;/p&gt;

&lt;h2 id=&quot;dependency-updates&quot;&gt;Dependency updates&lt;/h2&gt;
&lt;p&gt;After adding dependency scanning to your pipeline, a next step is to also update those dependencies regularly. Some teams have resolved to someone who runs all the updates every second Wednesday of the month. I like to wake up and see that an update was already checked for, a new branch for it created and already pushed to my repository, with a &lt;a href=&quot;https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests&quot;&gt;Pull Request&lt;/a&gt; to boot that already has ran all automatic checks to verify that:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;the build still works&lt;/li&gt;
  &lt;li&gt;tests automation still works (so no regressions)&lt;/li&gt;
  &lt;li&gt;there is no known vulnerability in the dependencies
The only thing left to do is approving the Pull Request, which some teams even do automatically: how easy can you make it on yourself?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Tools like &lt;a href=&quot;https://nukeeper.com/&quot;&gt;NuKeeper&lt;/a&gt; already have it available for the hosted versions of GitLab, Azure DevOps, GitHub and BitBucket for the .NET (Core) framework.
I’m working on getting the update part as automated as well and sharing that setup &lt;a href=&quot;https://github.com/rajbos/dependency-updates&quot;&gt;here&lt;/a&gt;, with for starters support for NPM and NuGet updates against a private GitLab environment.&lt;/p&gt;

&lt;h2 id=&quot;static-code-analysis&quot;&gt;Static Code Analysis&lt;/h2&gt;
&lt;p&gt;After automating your build another step to take is including Static Code Analysis. Tools like &lt;a href=&quot;https://www.sonarqube.org/&quot;&gt;SonarQube&lt;/a&gt;, &lt;a href=&quot;https://www.microfocus.com/en-us/products/static-code-analysis-sast/overview&quot;&gt;HPE Fortify&lt;/a&gt; or &lt;a href=&quot;https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/about-code-scanning&quot;&gt;GitHub’s CodeQL&lt;/a&gt; are very well known and used products.&lt;/p&gt;

&lt;p&gt;With Static Code Analysis you run an analyzer on your code to scan for known patterns. Most products have a set of patterns to use that can be extended when needed. The scan will find violations against the patterns and alert them to you. Most of them can then fail the build and additionally annotate a &lt;a href=&quot;https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests&quot;&gt;Pull Request&lt;/a&gt; for you, in an effort to prevent those violations to even become part of your code base (&lt;a href=&quot;https://www.youtube.com/watch?v=E4UD1dloNM8&amp;amp;list=PLXVVwOM8uv2wQyhQ7mB_Nv_iXyMuXf-GT&amp;amp;index=2&amp;amp;t=2s&quot;&gt;shift left&lt;/a&gt;).&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Missing stages in Azure DevOps YAML Pipelines</title>
			<link href="https://devopsjournal.io/blog/2020/12/06/Missing-stages-in-Azure-DevOps"/>
			<updated>2020-12-06T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2020/12/06/Missing-stages-in-Azure-DevOps</id>
			<content type="html">&lt;h1 id=&quot;alt-title-approval-to-an-environment-blocks-the-whole-pipeline&quot;&gt;Alt. title: approval to an environment blocks the whole pipeline&lt;/h1&gt;
&lt;p&gt;Sometimes you find out about something and feel rather stupid. This one is one of the reasons YAML pipelines often feel like you need a magic incantation to get things working the correct way. Since this took me way to long to figure out, I’m writing about it here to hopefully safe someone else a lot of time (probably my future self 😁). I have bumped into these incantations before: using different CI/CD systems seems to make me forget them 🤔.
&lt;img src=&quot;/images/2020/20201206/dan-meyers-dj2tR9dS3e8-unsplash.jpg&quot; alt=&quot;Stupid image of a T-Rex statue&quot; /&gt;&lt;/p&gt;
&lt;h5 id=&quot;photo-by-dan-meyers-on-unsplash&quot;&gt;&lt;span&gt;Photo by &lt;a href=&quot;https://unsplash.com/@dmey503?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Dan Meyers&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/s/photos/stupid?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/span&gt;&lt;/h5&gt;

&lt;h2 id=&quot;the-premise&quot;&gt;The premise&lt;/h2&gt;
&lt;p&gt;I was working on a multi-stage pipeline in Azure DevOps using YAML files. In the beginning I only had the Build part of my pipeline to build the solution. Wanting to deploy it, I added &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/pipelines/process/deployment-jobs?view=azure-devops?WT.mc_id=DOP-MVP-5003719&quot;&gt;deployment&lt;/a&gt; phases to my setup. To get them working, I made the mistake to see them as specific &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=azure-devops&amp;amp;tabs=schema%2Cparameter-schema&amp;amp;WT.mc_id=DOP-MVP-5003719&quot;&gt;jobs&lt;/a&gt;. [TLDR: the are not]:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
 &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;job&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Build&lt;/span&gt;
   &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
     &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;SomeTask&lt;/span&gt;

 &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;deployment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;DeployTest&lt;/span&gt;
   &lt;span class=&quot;na&quot;&gt;displayName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Deploy to test&lt;/span&gt;
   &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Test&lt;/span&gt;
   &lt;span class=&quot;na&quot;&gt;dependsOn&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Build&lt;/span&gt;

 &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;deployment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;DeployUAT&lt;/span&gt;
   &lt;span class=&quot;na&quot;&gt;displayName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Deploy to UAT&lt;/span&gt;
   &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;UAT&lt;/span&gt;
   &lt;span class=&quot;na&quot;&gt;dependsOn&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;DeployTest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can see that I needed to add &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/pipelines/process/conditions?view=azure-devops&amp;amp;tabs=yaml&amp;amp;WT.mc_id=DOP-MVP-5003719&quot;&gt;dependsOn&lt;/a&gt; to get the to work sequentially: not doing this would trigger all jobs at the same time (and subsequently fail the deploys, since the artifacts where not available).&lt;/p&gt;

&lt;h2 id=&quot;missing-stages&quot;&gt;Missing stages&lt;/h2&gt;
&lt;p&gt;Looking back, the first clue was that I didn’t get multiple stages in any overview page:
&lt;img src=&quot;/images/2020/20201206/20201206_01_MissingStages.png&quot; alt=&quot;Pipeline runs overview with only one stage visible&quot; /&gt;&lt;/p&gt;

&lt;p&gt;My confusion came from this screen and not making the connection.
The different ‘stages’ of my pipeline where showing here, so why not in the overview page?
&lt;img src=&quot;/images/2020/20201206/20201206_02_MultipleJobs.png&quot; alt=&quot;Job overview showing multiple jobs&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;finding-a-solution&quot;&gt;Finding a solution&lt;/h2&gt;
&lt;p&gt;Skipping the confusion part (I assumed I must have done something wrong or Azure DevOps changed something since I last checked), I continued with adding &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/pipelines/process/environments?view=azure-devops#approvals&amp;amp;WT.mc_id=DOP-MVP-5003719&quot;&gt;approvals&lt;/a&gt; to the environments my ‘stages’ where creating.&lt;/p&gt;

&lt;h2 id=&quot;environment-approval-blocks-all-jobs&quot;&gt;Environment approval blocks all jobs&lt;/h2&gt;
&lt;p&gt;Running the pipeline with an approval on one of the &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/pipelines/process/environments?view=azure-devops&amp;amp;WT.mc_id=DOP-MVP-5003719&quot;&gt;environments&lt;/a&gt;, blocked the &lt;strong&gt;whole pipeline&lt;/strong&gt;! Not just the job targeting that specific environment, as I was expecting.
&lt;img src=&quot;/images/2020/20201206/20201206_05_ApprovalBlocksAllJobs.png&quot; alt=&quot;Environment approval blocks all jobs&quot; /&gt;
Searching the web, I came across other people having sort of the &lt;a href=&quot;https://stackoverflow.com/questions/57321733/checks-approvals-for-a-deployment-job-are-blocking-the-entire-stage&quot;&gt;same issue&lt;/a&gt;, like for example this &lt;a href=&quot;https://developercommunity.visualstudio.com/idea/673881/dont-block-the-entire-stage-when-checks-approvals.html&quot;&gt;uservoice&lt;/a&gt;. An almost two year old feature request to prevent this scenario from happening?!? Surely this is not happening in Azure DevOps?&lt;/p&gt;

&lt;p&gt;Eventually it was this &lt;a href=&quot;https://stackoverflow.com/a/60810101/4395661&quot;&gt;Stack Overflow answer&lt;/a&gt; that finally pushed me in the right direction…&lt;/p&gt;

&lt;h2 id=&quot;some-more-hints-stage-picker-when-starting-a-pipeline&quot;&gt;Some more hints: Stage picker when starting a pipeline&lt;/h2&gt;
&lt;p&gt;I learned about a &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/release-notes/2019/sprint-162-update#skipping-stages-in-a-yaml-pipeline?WT.mc_id=DOP-MVP-5003719&quot;&gt;new feature&lt;/a&gt; that lets you pick the the stages to run when starting a pipeline run:
&lt;img src=&quot;/images/2020/20201206/20201206_03_StagesToRun.png&quot; alt=&quot;Pick stages to run on starting a pipeline manually&quot; /&gt;
This leads to this message, not making it that much more clear (oh hindsight):
&lt;img src=&quot;/images/2020/20201206/20201206_04_StagesToRun.png&quot; alt=&quot;Message: Configuration is only available for multi-stage pipelines.&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;the-fix&quot;&gt;The fix&lt;/h1&gt;
&lt;p&gt;This is where I felt stupid, shocked and reinforced the ‘magic incantation’ part of the &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/pipelines/get-started/pipelines-get-started?view=azure-devops#define-pipelines-using-yaml-syntax&amp;amp;WT.mc_id=DOP-MVP-5003719&quot;&gt;YAML pipelines&lt;/a&gt;…
The jobs relate to the same jobs in a &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/pipelines/get-started/pipelines-get-started?view=azure-devops#define-pipelines-using-the-classic-interface&amp;amp;WT.mc_id=DOP-MVP-5003719&quot;&gt;classic pipeline&lt;/a&gt; and you need to wrap them in stages to have an actual multi-stage pipeline!&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;stages&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;

&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Build&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;job&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Build&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;SomeTask&lt;/span&gt;

&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;DeployTest&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;deployment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;DeployTest&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;displayName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Deploy to test&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Test&lt;/span&gt;

&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;DeployUAT&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;deployment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;DeployUAT&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;displayName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Deploy to UAT&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;UAT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, the pipeline overview actually shows the correct number of stages:
&lt;img src=&quot;/images/2020/20201206/20201206_06_CorrectStagesDisplayed.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;And the approval works as planned:
&lt;img src=&quot;/images/2020/20201206/20201206_07_ApprovalsTheRightWay.png&quot; alt=&quot;Approvals only blocking the right stage&quot; /&gt;&lt;/p&gt;

&lt;p&gt;So: wondering why an approval on an environment is blocking your whole pipeline? Or wondering why you only see one ‘stage’ indicator in the pipeline runs overview? Know you know how to fix it.&lt;/p&gt;

&lt;h1 id=&quot;request&quot;&gt;Request:&lt;/h1&gt;
&lt;p&gt;Found this page with mostly the same searching around I was doing? Please let me know! Finding out these posts help someone else really makes my day. That also makes me feel less stupid 😁.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Use GitHub Actions with a private runner to deploy to IIS</title>
			<link href="https://devopsjournal.io/blog/2020/11/24/github-actions-with-private-runner-iis"/>
			<updated>2020-11-24T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2020/11/24/github-actions-with-private-runner-iis</id>
			<content type="html">&lt;p&gt;Recently I got asked if you could use GitHub Actions to deploy to an IIS web application which of course I had to test :grin:.&lt;/p&gt;

&lt;h1 id=&quot;tldr-it-runs-the-same-as-you-would-with-a-powershell-script&quot;&gt;TL;DR It runs the same as you would with a PowerShell script&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20201124/actions.png&quot; alt=&quot;GitHub Actions Logo&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;example&quot;&gt;Example&lt;/h2&gt;
&lt;p&gt;For testing this I used an example application in this &lt;a href=&quot;https://github.com/rajbos/dotnetcore-webapp/&quot;&gt;repo&lt;/a&gt; (you can find the actions there as well). It’s based on the following dotnet command:&lt;/p&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dotnet new webapp
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;flow&quot;&gt;Flow&lt;/h3&gt;
&lt;p&gt;Since this is a .NET Core application, the workflow for GitHub Actions has these steps:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Checkout the repo&lt;/li&gt;
  &lt;li&gt;Set the correct .NET Core version&lt;/li&gt;
  &lt;li&gt;dotnet build&lt;/li&gt;
  &lt;li&gt;dotnet publish&lt;/li&gt;
  &lt;li&gt;deploy to IIS&lt;/li&gt;
  &lt;li&gt;Run a smoketest&lt;/li&gt;
  &lt;li&gt;Run the webtests (added as an extra example, keep on reading for more info)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can find that workflow &lt;a href=&quot;https://github.com/rajbos/dotnetcore-webapp/blob/main/.github/workflows/dotnetcore-iis.yml&quot;&gt;here&lt;/a&gt;. 💡 If you want to see the workflow for pushing the application to an &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/app-service?WT.mc_id=AZ-MVP-5003719&quot;&gt;Azure App Service&lt;/a&gt;, check the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotnetcore.yml&lt;/code&gt; file next to it.&lt;/p&gt;

&lt;h3 id=&quot;setup&quot;&gt;Setup&lt;/h3&gt;
&lt;p&gt;For running the IIS commands I’ve used the most simple example, other command line options will work as well:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Stop the website (or the entire webserver in this case)&lt;/li&gt;
  &lt;li&gt;Overwrite all files&lt;/li&gt;
  &lt;li&gt;Start the website again
Using WebDeploy or a &lt;a href=&quot;https://docs.microsoft.com/en-us/powershell/scripting/learn/remoting/running-remote-commands?view=powershell-7.1&amp;amp;WT.mc_id=DOP-MVP-5003719&quot;&gt;remote PowerShell&lt;/a&gt; session will work as well. Find more explanation on remoting in this &lt;a href=&quot;/blog/2020/03/29/Deploy-locally-on-Windows-Azure-DevOps&quot;&gt;blogpost&lt;/a&gt; as well.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;action&quot;&gt;Action&lt;/h3&gt;
&lt;p&gt;The actual actions that ‘deploy’ the application are as follows.&lt;/p&gt;
&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Deploy to IIS&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;iisreset /stop&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;Copy-Item ./dotnetcorewebapp/* C:/inetpub/wwwroot/dotnetcore-webapp -Recurse -Force&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;iisreset /start&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h5 id=&quot;note-running-these-steps-requires-admin-level-access-rights-so-youll-need-to-run-the-self-hosted-runner-with-that-access-level-this-stems-from-the-appexec-commands-that-it-fires-that-require-that-level-of-access-still-an-unfortunate-thing&quot;&gt;Note: running these steps requires Admin level access rights, so you’ll need to run the self-hosted runner with that access level. This stems from the &lt;strong&gt;AppExec&lt;/strong&gt; commands that it fires that require that level of access (still an unfortunate thing).&lt;/h5&gt;

&lt;h3 id=&quot;private-github-action-runner&quot;&gt;Private GitHub Action Runner&lt;/h3&gt;
&lt;p&gt;To enable the deployment of the application on a Windows box, you’ll have to use a &lt;a href=&quot;https://docs.github.com/en/free-pro-team@latest/actions/hosting-your-own-runners/about-self-hosted-runners&quot;&gt;private GitHub action runner&lt;/a&gt; since the cloud hosted runners will not have access to that machine (they shouldn’t!). You can install them like a normal runner like for example Azure DevOps. Luckily the list of URL’s you need to add to your proxy/allow list is a lot shorter than the &lt;a href=&quot;/blog/2020/04/16/Run-Azure-DevOps-Agent-Behind-a-proxy&quot;&gt;Azure DevOps&lt;/a&gt; list.&lt;/p&gt;

&lt;p&gt;The runner runs on demand or as a Windows Service and will periodically open a long polling connection to GitHub, asking if there is work to do. The connection is always outgoing and on port 443.&lt;/p&gt;

&lt;p&gt;Installing a runner can be done from a repository, team or organization level from the website. Go to “Settings” –&amp;gt; Actions and scroll down to Self-hosted runners:
&lt;img src=&quot;/images/2020/20201124/20201124_01_SelfHostedRunners.png&quot; alt=&quot;Screenshot of the self-hosted runners view&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Adding a runner is made very easy, all the steps are listed right in the screen, even including the temporary token it uses for a one time authentication process:
&lt;img src=&quot;/images/2020/20201124/20201124_02_AddingARunner.png&quot; alt=&quot;Screenshot of the steps to add a self-hosted runner&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;bonus&quot;&gt;Bonus&lt;/h2&gt;
&lt;p&gt;The next question that came up was if you could run a Selenium WebTest (as I call that type of end-to-end test) with such a runner and if that would also work with a hosted runner. Long story short: it just works.&lt;/p&gt;

&lt;p&gt;In both &lt;a href=&quot;https://github.com/rajbos/dotnetcore-webapp/blob/main/.github/workflows/&quot;&gt;workflows&lt;/a&gt; I’ve added the last step ‘Run Web Test’ that runs the unit tests in the &lt;a href=&quot;https://github.com/rajbos/dotnetcore-webapp/blob/main/dotnet-core-webapp.webtests/UnitTest1.cs&quot;&gt;WebTest&lt;/a&gt; project that use a Selenium Driver to talk to the installed Chrome instance on the runner. You can find all the preinstalled software on the hosted runner &lt;a href=&quot;https://docs.github.com/en/free-pro-team@latest/actions/reference/specifications-for-github-hosted-runners#supported-software&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20201124/20201124_03_WebTest.png&quot; alt=&quot;Screenshot of webtest output&quot; /&gt;&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Azure Repos: Authenticate Git with a PAT</title>
			<link href="https://devopsjournal.io/blog/2020/11/08/Azure-DevOps-Git-Authenticate-With-PAT"/>
			<updated>2020-11-08T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2020/11/08/Azure-DevOps-Git-Authenticate-With-PAT</id>
			<content type="html">&lt;p&gt;Sometimes you have these weird things you run into, and I’m sure I will not be able to find this one if I don’t store it here.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20201108/hakan-aldrin-NSnXEpIl6xs-unsplash.jpg&quot; alt=&quot;Image of frozen binoculars&quot; /&gt;&lt;/p&gt;
&lt;h6 id=&quot;photo-by-hakan-aldrin&quot;&gt;Photo by &lt;a href=&quot;https://unsplash.com/@greystoke?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Hakan Aldrin&lt;/a&gt;&lt;/h6&gt;

&lt;h1 id=&quot;configuring-git-with-a-pat-token-with-azure-devops&quot;&gt;Configuring Git with a PAT token with Azure DevOps&lt;/h1&gt;
&lt;p&gt;Usually in Windows I use the Windows Credential Manager for storing authentication against remote Git repositories. You can also use the SSH setup that &lt;a href=&quot;https://dev.azure.com?WT.mc_id=DOP-MVP-5003719&quot;&gt;Azure DevOps&lt;/a&gt; supports as a widely used alternative.&lt;/p&gt;

&lt;p&gt;This time I was setting things up for a user with a Docker container and didn’t want to setup any of those options: I was already using a &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops?WT.mc_id=DOP-MVP-5003719&quot;&gt;Personal Access Token&lt;/a&gt; for accessing the &lt;a href=&quot;https://docs.microsoft.com/en-us/rest/api/azure/devops/?WT.mc_id=DOP-MVP-5003719&quot;&gt;REST API&lt;/a&gt; and wanted to reuse that for the Git repository as well.&lt;/p&gt;

&lt;p&gt;Searching around took quite a while until I found an obscure reference within the Git LFS repo that indicated you could setup Git with an extra authorization header with the PAT token in it. Seriously: can’t even find the repo I found this in.&lt;/p&gt;

&lt;h2 id=&quot;solution-repo-based&quot;&gt;Solution (repo based)&lt;/h2&gt;
&lt;p&gt;After some messing around I got things working, so here is the solution for future reference.&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kr&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;SetupAuthentication&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;param&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$organization&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$project&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$repoName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$userName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$PAT&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

    &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# convert the Personal Access Token to a Base64 encoded string&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$B64Pat&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Convert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ToBase64String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;System.Text.Encoding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UTF8.GetBytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$PAT&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

    &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# store the extra header for git to use&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;git&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--global&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--add&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;http.https://&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$userName&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dev.azure.com/&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$organization&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$project&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;/_git/&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$repoName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;extraHeader&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;AUTHORIZATION: Basic &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$B64Pat&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note that this is specific for the &lt;strong&gt;repository&lt;/strong&gt; you are using.&lt;/p&gt;

&lt;h2 id=&quot;solution-project-based&quot;&gt;Solution (project based)&lt;/h2&gt;
&lt;p&gt;If you want to skip configuring this for every repo, you can also leave the repo name off this setting like the example below:&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# store the extra header for git to use&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;git&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--global&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--add&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;http.https://&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$userName&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dev.azure.com/&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$organization&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$project&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;/_git/.extraHeader&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;AUTHORIZATION: Basic &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$B64Pat&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Note that this is specific for the &lt;strong&gt;project&lt;/strong&gt; you are using.&lt;/p&gt;

&lt;h2 id=&quot;solution-organization-based&quot;&gt;Solution (organization based)&lt;/h2&gt;
&lt;p&gt;If you want to skip configuring this for every repo, you can also leave the repo name off this setting like the example below:&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# store the extra header for git to use&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;git&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--global&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--add&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;http.https://&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$userName&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dev.azure.com/&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$organization&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;/_git/.extraHeader&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;AUTHORIZATION: Basic &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$B64Pat&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Note that this is specific for the &lt;strong&gt;organization&lt;/strong&gt; you are using.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Use NuKeeper &apos;manually&apos; to update NuGet packages</title>
			<link href="https://devopsjournal.io/blog/2020/11/03/use-nukeeper-manually-to-update-nuget-packages"/>
			<updated>2020-11-03T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2020/11/03/use-nukeeper-manually-to-update-nuget-packages</id>
			<content type="html">&lt;p&gt;While building up a scheduled pipeline for updating our &lt;a href=&quot;https://docs.microsoft.com/en-us/nuget?WT.mc_id=DOP-MVP-5003719&quot;&gt;NuGet&lt;/a&gt; dependencies I found &lt;a href=&quot;https://github.com/NuKeeperDotNet/NuKeeper/&quot;&gt;NuKeeper&lt;/a&gt; to help with automatically updating the packages we use. If you don’t know &lt;a href=&quot;https://github.com/NuKeeperDotNet/NuKeeper/&quot;&gt;NuKeeper&lt;/a&gt;, it is an open source project that will go through your .NET solution and check with the configured NuGet sources to see if there are any packages that have available updates. If there are updates, it can update your project (or solution) and even generate a Pull Request for you.&lt;/p&gt;

&lt;p&gt;With the DevOps mindset of handling these updates as technical debt, I wanted to run this check automatically each day, get any updates if possible and start our build pipeline to verify if it works.&lt;/p&gt;

&lt;h5 id=&quot;side-note-it-uses-the-normal-nuget-flow-to-check-for-updates-and-uses-that-flow-for-authenticatedprivate-package-sources-as-well&quot;&gt;Side note: it uses the normal NuGet flow to check for updates and uses that flow for authenticated/private package sources as well.&lt;/h5&gt;

&lt;p&gt;NuKeeper can handle multiple platforms as you can see below. Currently I was running this on GitLab, so the examples given here might need some small tweaks to push the Pull Request for a different platform. The manual check with NuKeeper will work regardless of the platform.
&lt;img src=&quot;/images/2020/20201103/20201103NuKeeperSupport.png&quot; alt=&quot;NuKeeper supported platforms&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;flow&quot;&gt;Flow&lt;/h2&gt;
&lt;p&gt;With NuKeeper the normal flow is usually just a one line command:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;nukeeper repo &amp;lt;repo url&amp;gt; &amp;lt;token&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;the-issue&quot;&gt;The issue&lt;/h2&gt;
&lt;p&gt;The setup I’m working with has an on-premises &lt;a href=&quot;https://gitlab.com&quot;&gt;GitLab&lt;/a&gt; instance. Even thought NuKeepers &lt;a href=&quot;https://nukeeper.com/platform/gitlab/&quot;&gt;documentation&lt;/a&gt; states that they support GitLab, it is only the cloud version hosted by GitLab.com. When you use the &lt;a href=&quot;https://github.com/NuKeeperDotNet/NuKeeper/&quot;&gt;nukeeper repo&lt;/a&gt; commands, you’ll get an error that the url is not a known platform. After adding a parameter indicating it is a GitLab environment, it will tell you that it cannot execute against that url.&lt;/p&gt;

&lt;h2 id=&quot;solution&quot;&gt;Solution&lt;/h2&gt;
&lt;p&gt;To still get everything working, I needed to set things up manually with this flow:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1. Check for updates on the project/solution
2. If there are no updates, stop the run
3. If there are updates, pull them in
4. Create a new branch
5. Commit and push the changes to origin
6. Create a Pull Request
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Since we already have a continuous integration pipeline setup, the pull request automatically starts a pipeline that will build and test the solution, so that will make sure the incoming changes don’t break anything.&lt;/p&gt;

&lt;h1 id=&quot;checking-with-nukeeper-manually&quot;&gt;Checking with NuKeeper manually&lt;/h1&gt;
&lt;p&gt;I wanted to run NuKeeper and then find out if there are updates available. Currently this is not possible to do in one call: there are some output parameters available, but nothing that is easily usable out of the box.&lt;/p&gt;

&lt;p&gt;I used the output to csv parameter, that at least returns all output as an array that I can loop through&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# get update info&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$updates&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;\nukeeper&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;inspect&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--outputformat&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;csv&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then you can do a (most ugly) search through the results to see if there are any updates found by NuKeeper:&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# since the update info is in csv, we&apos;ll need to search&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$updatesFound&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;$false&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;foreach&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$row&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$updates&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$row&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;IndexOf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;possible updates&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-gt&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Found updates row [&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$row&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;]&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$row&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;IndexOf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;0 possible updates&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-gt&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;There are no updates&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;There are updates&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$updatesFound&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;$true&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When updates where found, I can create the new branch and create a Pull Request by hand:&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;w&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$branchName&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CreateNewBranch&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;UpdatePackages&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CommitAndPushBranch&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-branchName&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$branchName&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CreateMergeRequest&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-branchName&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$branchName&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-branchPrefix&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$branchPrefix&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-gitLabProjectId&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$gitLabProjectId&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In UpdatePackages I use the NuKeeper command to update the solution/project:&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kr&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;UpdatePackages&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;\nukeeper&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The full code is available in this &lt;a href=&quot;https://gist.github.com/rajbos/c4ff9619b9da7dd7f9062d69e0d364e5&quot;&gt;GitHub Gist&lt;/a&gt;. I’ve split this up between the NuKeeper code (nukeeper.ps1) and the GitLab code (gitlab.ps1) for easier reuse.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Git clone error: Filename too long on Windows 10</title>
			<link href="https://devopsjournal.io/blog/2020/09/23/Git-clone-Filename-too-long-Windows"/>
			<updated>2020-09-23T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2020/09/23/Git-clone-Filename-too-long-Windows</id>
			<content type="html">&lt;p&gt;Today I ran into an issue that I tried to clone a Git repository with large filenames/folder paths in it.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;fatal: cannot create directory at &apos;src/Modules/&amp;lt;long path here&amp;gt;&apos;: Filename too long
warning: Clone succeeded, but checkout failed.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20200923/federico-beccari-cyg3DD6Y69A-unsplash.jpg&quot; alt=&quot;Photo of windy road with car lights in long exposure&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The folder path display was only 195 characters long, but adding my root folder with 38 characters got awfully close to the known &lt;a href=&quot;https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?WT.mc_id=DOP-MVP-5003719&quot;&gt;260 characters limit in Windows&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id=&quot;fixing-the-issue&quot;&gt;Fixing the issue&lt;/h1&gt;
&lt;p&gt;To fix this you need to do two things:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Tell Windows to support long file paths&lt;/li&gt;
  &lt;li&gt;Tell Git to support long file paths&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;configure-windows-for-long-file-paths&quot;&gt;Configure Windows for long file paths:&lt;/h2&gt;

&lt;p&gt;You can do this either by updating the local Group Policy Setting through the &lt;a href=&quot;https://docs.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/how-to-configure-security-policy-settings?WT.mc_id=DOP-MVP-5003719&quot;&gt;Editor&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1. Windows Run --&amp;gt; gpedit.msc
2. Computer Configuration &amp;gt; Administrative Templates &amp;gt; System &amp;gt; Filesystem &amp;gt; Enable Win32 long paths
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Or by using the &lt;a href=&quot;https://support.microsoft.com/en-us/help/4027573/windows-10-open-registry-editor?WT.mc_id=DOP-MVP-5003719&quot;&gt;registry editor&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1. Windows Run --&amp;gt; regedit
2. Path:  HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem
Key name: LongPathsEnabled
Value: 1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;After updating this setting you either need to Sign Out and back on, or reboot the machine.&lt;/p&gt;

&lt;h2 id=&quot;configure-git-for-long-file-paths&quot;&gt;Configure Git for long file paths&lt;/h2&gt;
&lt;p&gt;Git doesn’t know about the changes in Windows and is installed by default without the LongPath setup we need. Enable it from the command line:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git config --system core.longpaths true
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now you can clone the repository again.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Fixing .NET Core tool update issues</title>
			<link href="https://devopsjournal.io/blog/2020/09/18/fixing-dotnet-core-update-issues"/>
			<updated>2020-09-18T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2020/09/18/fixing-dotnet-core-update-issues</id>
			<content type="html">&lt;p&gt;I had an issue running an  &lt;a href=&quot;https://docs.microsoft.com/en-us/ef/?WT.mc_id=DOP-MVP-5003719&quot;&gt;EntityFramework&lt;/a&gt; command after updating the NuGet packages to the latest version and searching got me to multiple steps and sites to get things fixed. Grouping them here for future reference.&lt;/p&gt;

&lt;p&gt;If it helps you as well, please let me know! Always nice to see these posts helping someone else as well.&lt;/p&gt;

&lt;h1 id=&quot;the-trigger-message&quot;&gt;The trigger message&lt;/h1&gt;
&lt;p&gt;This was the initial message that got me on this path:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;The EF Core tools version &apos;2.2.0-rtm-35687&apos; is older than that of the runtime &apos;3.1.8&apos;. Update the tools for the latest features and bug fixes.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20200918/clay-banks-LjqARJaJotc-unsplash.jpg&quot; alt=&quot;Image of hands&quot; /&gt;&lt;/p&gt;
&lt;h4 id=&quot;photo-by-clay-banks-on-unsplash&quot;&gt;&lt;span&gt;Photo by &lt;a href=&quot;https://unsplash.com/@claybanks?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Clay Banks&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/span&gt;&lt;/h4&gt;

&lt;h1 id=&quot;use-the-search-luke&quot;&gt;Use the search Luke!&lt;/h1&gt;
&lt;p&gt;The number one skill you need as a developer / engineer / person who needs to do this, is asking for help. Learning how to use your google skills to find solution is one way to do it. Even after ~15 years of programming I do this all day long.&lt;/p&gt;

&lt;p&gt;For this one, I had to use multiple sources to find a fix.
&lt;img src=&quot;/images/2020/20200918/20200918_01_SearchResults.png&quot; alt=&quot;Image of all the tabs to get this figured out&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;net-core-tools&quot;&gt;.NET Core Tools&lt;/h1&gt;
&lt;p&gt;A &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/core/tools/global-tools?WT.mc_id=DOP-MVP-5003719&quot;&gt;.NET Core tool&lt;/a&gt; is a special NuGet package that contains a console application and can be installed in several ways:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;As a global tool&lt;/li&gt;
  &lt;li&gt;As a global tool in a custom location&lt;/li&gt;
  &lt;li&gt;As a local tool (from version 3.0 and up)
In this case I am using the default, so a global tool.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;updating-net-core-tools&quot;&gt;Updating .NET Core tools.&lt;/h1&gt;
&lt;p&gt;Installing or updating any global &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/core/tools/global-tools?WT.mc_id=DOP-MVP-5003719#update-a-tool&quot;&gt;.NET Core tool&lt;/a&gt; can be done with the same command:&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;err&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dotnet&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;tool&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--global&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dotnet-ef&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h4 id=&quot;do-note-you-can-use-donet-tool-install-as-well-but-the-update-command-does-the-same-thing-and-will-install-the-tool-if-it-is-not-installed-yet&quot;&gt;Do note: you can use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;donet tool install&lt;/code&gt; as well, but the update command does the same thing and will install the tool if it is not installed yet.&lt;/h4&gt;

&lt;h1 id=&quot;errors-during-the-update&quot;&gt;Errors during the update:&lt;/h1&gt;
&lt;p&gt;Unfortunately running the update resulted in the following error. Seems like we are going down the rabbit hole again…&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;err&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dotnet&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;tool&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--global&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dotnet-ef&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Tool&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;dotnet-ef&apos;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;has&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;multiple&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;versions&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;installed&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;cannot&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;be&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;updated.&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;fixing-the-errors&quot;&gt;Fixing the errors&lt;/h1&gt;
&lt;p&gt;The &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/core/tools/global-tools?WT.mc_id=DOP-MVP-5003719&quot;&gt;.NET Core tools&lt;/a&gt; are installed in your user folder by default. There is no centralized storage of what versions are installed, the commands will be found if they can be found in this folder. So, you can look what it contains:
&lt;img src=&quot;/images/2020/20200918/20200918_02_NET_Tools.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Each tool has its own folder and in that are folders for each version.
Since there is no centralized storage (the tools folder can be seen as storage of course), deleting the folder for the tool can be used as a first step. You can try to remove a specific version in this folder, but I decided to delete it completely.&lt;/p&gt;
&lt;h4 id=&quot;note-i-had-a-preview-version-in-here-that-might-have-been-the-cause-of-things&quot;&gt;Note: I had a preview version in here, that might have been the cause of things.&lt;/h4&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;del&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;C:\Users\&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;USERNAME&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;\.dotnet\tools\.store\dotnet-ef&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After deleting that folder and running the &lt;a href=&quot;https://docs.microsoft.com/en-us/ef/?WT.mc_id=DOP-MVP-5003719&quot;&gt;EntityFramework&lt;/a&gt; command again, you probably get the same error as I had:&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;Failed&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;shell&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;shim&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;tool&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;dotnet-ef&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Command&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;dotnet-ef&apos;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;conflicts&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;an&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;existing&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;another&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;tool.&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Tool&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;dotnet-ef&apos;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;failed&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;install.&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;That folder holds information about the version of the tool installed and not the console application itself.
Turns out you also need to remove the executable itself. Makes sense when you find that out, but it was not that intuitive the first time.&lt;/p&gt;

&lt;p&gt;So remove the executable:&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;del&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;C:\Users\&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;USERNAME&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;\.dotnet\tools\dotnet-ef.exe&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now you can run the update again. You can specify a version if you want to or leave it off to get the latest.&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;err&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dotnet&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;tool&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--global&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dotnet-ef&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--version&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;3.1.8&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
		</entry>
	
		<entry>
			<title>ADAL error in Azure DevOps API interaction</title>
			<link href="https://devopsjournal.io/blog/2020/08/07/ADAL-error-Azure-DevOps"/>
			<updated>2020-08-07T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2020/08/07/ADAL-error-Azure-DevOps</id>
			<content type="html">&lt;p&gt;Today I encountered an issue while interacting with the &lt;a href=&quot;https://docs.microsoft.com/en-us/rest/api/azure/devops/?view=azure-devops-rest-6.1&amp;amp;WT.mc_id=DOP-MVP-5003719&quot;&gt;Azure DevOps API&lt;/a&gt;. In the end this is not an issue with the API but with the user authentication and verification of tokens.
Since it took me a while to figure out what was happening, I’m documenting it here.&lt;/p&gt;

&lt;h2 id=&quot;error-message-failed-to-obtain-an-access-token-of-identity--the-refresh-token-has-expired-due-to-inactivity&quot;&gt;Error message ‘Failed to obtain an access token of identity …, The refresh token has expired due to inactivity’&lt;/h2&gt;
&lt;p&gt;It matched the current project that I was working on: it had been a while since I used that project (and Azure DevOps organization for that matter) last: at least 5 months ago.
The weird thing is, other calls to the REST API where working, using the same Personal Access Token (&lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&amp;amp;tabs=preview-page&amp;amp;WT.mc_id=DOP-MVP-5003719&quot;&gt;PAT&lt;/a&gt;) to setup projects, create repositories and other calls.
The script that failed was adding new users from the &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/active-directory/fundamentals/active-directory-whatis?WT.mc_id=AZ-MVP-5003719&quot;&gt;AAD&lt;/a&gt; to our project, calling this endpoint with a body that holds the full username (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;username@domain.tld&lt;/code&gt;) with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Invoke-RestMethod&lt;/code&gt; from PowerShell:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;https://vssps.dev.azure.com/&amp;lt;&amp;lt;organization&amp;gt;&amp;gt;/_apis/graph/users?api-version=5.0-preview.1&quot;`
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20200807/stephen-leonardi-wPlzrculha8-unsplash.jpg&quot; alt=&quot;Hero image: Person jumping in front of a tree&quot; /&gt;&lt;/p&gt;
&lt;h5 id=&quot;photo-by-stephen-leonardi&quot;&gt;&lt;span&gt;Photo by &lt;a href=&quot;https://unsplash.com/@stephenleo1982?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Stephen Leonardi&lt;/a&gt;&lt;/span&gt;&lt;/h5&gt;

&lt;h3 id=&quot;full-error-message&quot;&gt;Full error message&lt;/h3&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Microsoft.VisualStudio.Services.Aad.AadAccessSilentException:
GetUserAccessToken: Failed to obtain an access token of identity ea9d0e15-1e40-7030-b823-2612135e0b31. AAD returned silent failure.
Microsoft.IdentityModel.Clients.ActiveDirectory.AdalSilentTokenAcquisitionException:
Failed to acquire token silently as no token was found in the cache.

Call method AcquireToken
Microsoft.IdentityModel.Clients.ActiveDirectory.AdalServiceException: AADSTS700082: The refresh token has expired due to inactivity.
The token was issued on 2020-02-29T21:32:27.8019754Z and was inactive for 90.00:00:00.
Trace ID: &amp;lt;&amp;lt; GUID &amp;gt;&amp;gt;
Correlation ID: &amp;lt;&amp;lt; GUID &amp;gt;&amp;gt;
Timestamp: 2020-08-06 07:39:38Z
 Microsoft.IdentityModel.Clients.ActiveDirectory.AdalServiceException: Response status code does not indicate success: 400 (BadRequest)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The interesting parts are below:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Failed to obtain an access token of identity &amp;lt;&amp;lt; ID &amp;gt;&amp;gt;. AAD returned silent failure
The refresh token has expired due to inactivity.
The token was issued on 2020-02-29T21:32:27.8019754Z and was inactive for 90.00:00:00.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;the-cause&quot;&gt;The cause&lt;/h3&gt;
&lt;p&gt;From the error there seems to be something wrong with the user we are using for talking to the REST API, but what? Seems like that user hasn’t logged on in the last 90 days. It could not be my own account, because I’m using that to verify the projects are created correctly! Is it something with my PAT? I was able to find and add users from the AAD through the UI, so my account seemed fine. I even checked the link with the AAD to make sure nothing had happened with that connection.&lt;/p&gt;

&lt;h4 id=&quot;finding-the-user&quot;&gt;Finding the user&lt;/h4&gt;
&lt;p&gt;I’m not sure how to find back the user from the PAT token or that error: searching around in the AAD and Azure DevOps (even using the users API) didn’t track back the specific user. If you happen to know a way to do this, please let me know! I’ve only found that specific Id in the network log of the ‘Manage User’ functionality in Azure DevOps:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20200807/20200807_ManageUsers.png&quot; alt=&quot;Manage users pane with network tab open in browser&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Luckily it happened to be that I could be using the PAT from two users in this project: either my own, or the one from the service user we created for all the automation. I also happened to have logged the specific PAT Token in my local &lt;a href=&quot;https://keepass.info/&quot;&gt;KeePass&lt;/a&gt; file that I use for these things. I’ve even learned to note down the creation or ‘valid until’ date for them, so I have something to cross reference in case of things like this!&lt;/p&gt;

&lt;h2 id=&quot;the-fix&quot;&gt;The fix&lt;/h2&gt;
&lt;p&gt;After finding the user, the quick fix was easy: log in with that user!&lt;/p&gt;

&lt;p&gt;The only long term fix that I can think of will be periodically logging in with that user to the Azure DevOps organization, just to keep that account alive.&lt;/p&gt;

&lt;p&gt;This also helps with keeping scheduled builds for that user alive and prevent them from no longer starting. That is another common error people run into with this 90 day period.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Register a Startup or Shutdown script on Windows with PowerShell</title>
			<link href="https://devopsjournal.io/blog/2020/07/15/Register-windows-startup-shutdown-script"/>
			<updated>2020-07-15T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2020/07/15/Register-windows-startup-shutdown-script</id>
			<content type="html">&lt;p&gt;Today I got asked how you could register a Startup and/or Shutdown script on Windows through PowerShell.
My colleague already had a setup for creating a VM, but wanted this extra step as well.&lt;/p&gt;

&lt;p&gt;Searching the web revealed some bits and pieces, so I’m logging it here for future reference.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20200715/daniel-oberg-sEApBUS4fIk-unsplash.jpg&quot; alt=&quot;Nurturing plants image&quot; /&gt;&lt;/p&gt;
&lt;h5 id=&quot;photo-by-daniel-öberg&quot;&gt;&lt;span&gt;Photo by &lt;a href=&quot;https://unsplash.com/@artic_studios?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Daniel Öberg&lt;/a&gt;&lt;/span&gt;&lt;/h5&gt;

&lt;h2 id=&quot;gist&quot;&gt;Gist&lt;/h2&gt;
&lt;p&gt;I’ve created this &lt;a href=&quot;https://gist.github.com/rajbos/49f70f4e2b9765da05f0526225de2450&quot;&gt;gist&lt;/a&gt; with the registration script and an example file with a script to execute on Startup or Shutdown of the VM. It will then log the date/time to a text file for easy testing:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20200715/20200715_01_Output.png&quot; alt=&quot;Log messages in output text file&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The trick is to set up (quite a lot of registry keys) and a windows folder that has to be present to get things working.&lt;/p&gt;

&lt;h2 id=&quot;tested&quot;&gt;Tested&lt;/h2&gt;
&lt;p&gt;Tested on a Windows 10 VM (1909).&lt;/p&gt;

&lt;h2 id=&quot;caveat&quot;&gt;Caveat:&lt;/h2&gt;
&lt;p&gt;You need to run the register script with an elevated session because you need to have access to at least the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%SYSTEMROOT%&lt;/code&gt; directory.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Missing wiki repo in Azure DevOps</title>
			<link href="https://devopsjournal.io/blog/2020/07/14/Missing-wiki-repo-in-Azure-DevOps"/>
			<updated>2020-07-14T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2020/07/14/Missing-wiki-repo-in-Azure-DevOps</id>
			<content type="html">&lt;p&gt;Today someone in the Azure DevOps Club &lt;a href=&quot;https://teamservices.club/&quot;&gt;slack&lt;/a&gt; asked a question about finding the repo from the default wiki in Azure DevOps.
This used to be available if you knew what to do, so you could clone the repo and add pages programmatically for example.
Weirdly enough, we couldn’t find how to get the repo to be visual so we could use it.
In this case, the person asking the question wanted to add branch policies on the wiki repo so they can enforce Pull Requests on incoming changes.
Of course, I was intrigued and started to search: this functionality was always there before, so surely this will still be available?&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20200714/matthew-henry-2Ts5HnA67k8-unsplash.jpg&quot; alt=&quot;Picture of a dog (Pugg) with &apos;stress&apos;&quot; /&gt;&lt;/p&gt;
&lt;h5 id=&quot;photo-by-matthew-henry&quot;&gt;&lt;span&gt;Photo by &lt;a href=&quot;https://unsplash.com/@matthewhenry?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Matthew Henry&lt;/a&gt;&lt;/span&gt;)&lt;/h5&gt;

&lt;h2 id=&quot;tldr&quot;&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;Short version: it is not available anywhere, but you can ‘guess’ the correct URL and clone it:
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git clone https://dev.azure.com/&amp;lt;organization&amp;gt;/&amp;lt;project&amp;gt;/_git/&amp;lt;name of wiki&amp;gt;.wiki&lt;/code&gt;&lt;/p&gt;

&lt;h2 id=&quot;wiki-types&quot;&gt;Wiki types&lt;/h2&gt;
&lt;p&gt;In Azure DevOps there is a distinction between two ways to setup your wiki:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;The default (old) way of creating a wiki. It is a Git repo under the covers&lt;/li&gt;
  &lt;li&gt;Publish one or more repositories as a wiki. You can find more documentation &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/project/wiki/provisioned-vs-published-wiki?view=azure-devops?WT.mc_id=DOP-MVP-5003719&quot;&gt;here&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;history&quot;&gt;History&lt;/h2&gt;
&lt;p&gt;If you created a wiki a couple of years ago, you have the first wiki type. You could get the git URL to clone the repo and when you made changes to it, the repository would become visible on the Repos overview. This is no longer the case.&lt;/p&gt;

&lt;p&gt;Currently when you create a new team project, you get the option to choose between the two types, although it is not very clear to see the difference between the two.&lt;/p&gt;
&lt;h5 id=&quot;note-i-find-the-two-types-very-confusing-for-the-user-and-there-is-not-a-clear-way-to-use-them-the-same-way-it-would-be-better-if-a-project-admin-could-choose-to-include-the-repository-in-the-normal-overview-or-not-if-the-team-using-it-is-mature-enough-to-use-it-as-a-normal-repository-then-why-limit-its-use&quot;&gt;Note: I find the two types very confusing for the user and there is not a clear way to use them the same way. It would be better if a Project Admin could choose to include the repository in the normal overview or not. If the team using it is mature enough to use it as a normal repository, then why limit its use?&lt;/h5&gt;

&lt;h3 id=&quot;current-wiki-creation&quot;&gt;Current Wiki creation&lt;/h3&gt;
&lt;p&gt;When you create a new project and navigate to the wiki page, you are now greeted with this screen. Already confusing and the &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/project/wiki/provisioned-vs-published-wiki?view=azure-devops?WT.mc_id=DOP-MVP-5003719&quot;&gt;Learn more&lt;/a&gt; link tries to make the difference more clear, but doesn’t really make it clear it will always be a repo underneath.
&lt;img src=&quot;/images/2020/20200714/20200714_01_NewProject.png&quot; alt=&quot;New project screen&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I mean, if you need this large a matrix to try and make the differences clear, while under the covers it is the same setup, why not make your product easier to use?&lt;br /&gt;
&lt;img src=&quot;/images/2020/20200714/20200714_02_Docs.png&quot; alt=&quot;Difference matrix screenshot from the docs&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;searching-for-the-repo&quot;&gt;Searching for the repo&lt;/h2&gt;
&lt;p&gt;Testing things out, I created a new team project [Demo] and created a new project wiki for it.
Then I started searching for the wiki in the repos overview, but it only shows the default, empty project repository, not the wiki repo:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20200714/20200714_04_ReposDropdown.png&quot; alt=&quot;Azure Repos dropdown that doesn&apos;t show the wiki repo, only the default&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If you go to the wiki, it shows the name of the wiki:&lt;br /&gt;
&lt;img src=&quot;/images/2020/20200714/20200714_03_ProjectWiki.png&quot; alt=&quot;New wiki created with wiki name highlighted&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Even the dropdown or extra button (the three dots) doesn’t some more information. You &lt;strong&gt;can&lt;/strong&gt; find the git URL for cloning you need, but not how to get to the repository to set up branch policies for example…&lt;/p&gt;

&lt;h3 id=&quot;rest-api&quot;&gt;REST API&lt;/h3&gt;
&lt;p&gt;Azure DevOps has an awesome REST API you can use to automate almost everything in Azure DevOps, so let’s see what it returns.&lt;/p&gt;

&lt;p&gt;If you update the URL in your browser, you can test the API with normal GET request without setting up to much stuff. Go to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://dev.azure.com/raj-bos/Demo/_apis/git/repositories&lt;/code&gt; (so &lt;em&gt;organization&lt;/em&gt;/&lt;em&gt;project&lt;/em&gt;/_apis/git/repositories) and you get a list of repositories.&lt;br /&gt;
&lt;img src=&quot;/images/2020/20200714/20200714_05_API_ReposCall.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h5 id=&quot;note-this-does-not-include-the-wiki-repo-only-the-default-empty-repo-with-the-same-name-as-the-project&quot;&gt;Note this does not include the wiki repo, only the default empty repo with the same name as the project.&lt;/h5&gt;

&lt;h3 id=&quot;include-hidden-repositories&quot;&gt;Include Hidden repositories&lt;/h3&gt;
&lt;p&gt;If you include the query string &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;includeHidden=True&lt;/code&gt; as can be found in the &lt;a href=&quot;https://docs.microsoft.com/en-us/rest/api/azure/devops/git/repositories/list?view=azure-devops-rest-5.1&amp;amp;WT.mc_id=DOP-MVP-5003719&quot;&gt;API docs&lt;/a&gt;, you see that the wiki repo is visible:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20200714/20200714_06_API_ReposCall_Hidden.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Conclusion: it is a repo, but a hidden one!&lt;/p&gt;

&lt;p&gt;I’ve searched and tested some options, but I didn’t manage to &lt;a href=&quot;https://docs.microsoft.com/en-us/rest/api/azure/devops/git/repositories/update?view=azure-devops-rest-5.1&amp;amp;WT.mc_id=DOP-MVP-5003719&quot;&gt;update&lt;/a&gt; the repo and make it not hidden anymore.&lt;/p&gt;

&lt;p&gt;Finding some really old posts and a GitHub issue that requested the hidden repo to be visible (that got redirected to a UserVoice request that was closed due to inactivity 😧), I figured that this old URL might still be working… And luckily it is!&lt;/p&gt;

&lt;h1 id=&quot;the-fix&quot;&gt;The fix&lt;/h1&gt;
&lt;p&gt;If you check the name of your wiki repository, you can enter it in the URL of a normal repository (use the repo selection dropdown first for the correct URL to appear for easy changing):
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://dev.azure.com/&amp;lt;organization&amp;gt;/&amp;lt;project&amp;gt;/_git/&amp;lt;name of wiki&amp;gt;.wiki&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20200714/20200714_07_WikiRepo.png&quot; alt=&quot;Wiki repo visible&quot; /&gt;&lt;/p&gt;
&lt;h5 id=&quot;note-that-the-dropdown-still-doesnt-show-the-wiki-repo-it-never-will-currently&quot;&gt;Note that the dropdown still doesn’t show the wiki repo: it never will currently&lt;/h5&gt;

&lt;p&gt;The most amazing part: the UI will now remember the last repo you have viewed, so if you use the menu to navigate to branches, it will enable you to set branch policies. You can add the repo to the URL here by hand as well of course, if need be.&lt;/p&gt;

&lt;p&gt;Hopefully the Azure DevOps team improves on this omission (in my opinion) soon.&lt;/p&gt;

&lt;h1 id=&quot;summary&quot;&gt;Summary&lt;/h1&gt;
&lt;p&gt;So, in conclusion: if you can’t find the wiki repo in Azure DevOps, you now have a way to get to it, even when the UI doesn’t give you an option for it.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Using EntityFramework Core tooling with .NET Standard</title>
			<link href="https://devopsjournal.io/blog/2020/04/23/EntityFramework-Core-NET-Standard-Migrations"/>
			<updated>2020-04-23T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2020/04/23/EntityFramework-Core-NET-Standard-Migrations</id>
			<content type="html">&lt;p&gt;I want to target .NET Standard so I can always use my libraries in any project later on, independently of its target framework (as long as it supports the .NET Standard version I’m targeting).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20200429/hans-vivek-NMv5zwS3fVA-unsplash.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h6 id=&quot;unsplash-logophoto-by-hans-vivek&quot;&gt;&lt;a style=&quot;background-color:black;color:white;text-decoration:none;padding:4px 6px;font-family:-apple-system, BlinkMacSystemFont, &amp;quot;San Francisco&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Ubuntu, Roboto, Noto, &amp;quot;Segoe UI&amp;quot;, Arial, sans-serif;font-size:12px;font-weight:bold;line-height:1.2;display:inline-block;border-radius:3px&quot; href=&quot;https://unsplash.com/@oneshotespresso?utm_medium=referral&amp;amp;utm_campaign=photographer-credit&amp;amp;utm_content=creditBadge&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; title=&quot;Photo from Hans Vivek&quot;&gt;&lt;span style=&quot;display:inline-block;padding:2px 3px&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; style=&quot;height:12px;width:auto;position:relative;vertical-align:middle;top:-2px;fill:white&quot; viewBox=&quot;0 0 32 32&quot;&gt;&lt;title&gt;unsplash-logo&lt;/title&gt;&lt;path d=&quot;M10 9V0h12v9H10zm12 5h10v18H0V14h10v9h12v-9z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;&lt;span style=&quot;display:inline-block;padding:2px 3px&quot;&gt;Photo by Hans Vivek&lt;/span&gt;&lt;/a&gt;&lt;/h6&gt;

&lt;p&gt;Today I had an issue with using the Entity Framework Core tools in a .NET Standard Library: the EF Core tools don’t support the .NET Standard framework: they can only target .NET Core or .NET Classic (Full framework).&lt;/p&gt;

&lt;p&gt;This means that when you use a .NET Standard project to host your database setup in, you will get a nice error message when you run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotnet ef migrations add InitialCreate&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Startup project &apos;Provisioning.DataLibrary.csproj&apos; targets framework &apos;.NETStandard&apos;. There is no runtime associated with this framework, and projects targeting it cannot be executed directly. To use the Entity Framework Core .NET Command-line Tools with this project, add an executable project targeting .NET Core or .NET Framework that references this project, and set it as the startup project using --startup-project; or, update this project to cross-target .NET Core or .NET Framework. For more information on using the EF Core Tools with .NET Standard projects, see https://go.microsoft.com/fwlink/?linkid=2034781
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The documentation &lt;a href=&quot;https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/dotnet#other-target-frameworks?WT.mc_id=DOP-MVP-5003719&quot;&gt;link&lt;/a&gt; does point you in the right direction, but it wasn’t as easy to find. Next time I run into this issue, I should be able to find the solution quicker 😄.&lt;/p&gt;

&lt;h5 id=&quot;do-note-i-happen-to-still-have-the-version-220-rtm-35687-of-the-ef-core-tools-installed-not-sure-how-this-behavior-is-with-the-newer-versions&quot;&gt;Do note: I happen to still have the version ‘2.2.0-rtm-35687’ of the EF Core tools installed, not sure how this behavior is with the newer versions.&lt;/h5&gt;

&lt;h2 id=&quot;solution-setup&quot;&gt;Solution setup&lt;/h2&gt;
&lt;p&gt;The docs already indicate to create a dummy project with a dependency on the .NET Standard Library. What they don’t clearly explain, is that you then need to do some extra steps to get the EF Core tooling (like migrations) working.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20200429/20200429_SolutionSetup.png&quot; alt=&quot;Screenshot of the solution folders&quot; /&gt;
I’ve setup my solution like above:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Provisioning.DataLibrary holds the DbContext with all of its models and targets .NET Standard 2.0 in this case.&lt;/li&gt;
  &lt;li&gt;Provisioning.ConsoleApp is the dummy project with a dependency on the DataLibrary and targets .NET Core 3.1.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You need to add the EF Core designer package to the dummy project for it to get all the commands you want to use:&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;cd&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Provisioning.ConsoleApp&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dotnet&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;package&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Microsoft.EntityFrameworkCore.Design&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Also note that I am using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;appsettings.json&lt;/code&gt; to read the connection string to the database to use for EntityFramework, so I’ve included them to the console app: that is what EF Core will be using for running everything, so it needs to find those files as well (don’t forget to mark them as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Copy always&lt;/code&gt; to actually get them in the correct bin folder.).&lt;/p&gt;

&lt;p&gt;I usually open the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Package Manager Console&lt;/code&gt; to execute actions like calling the EF Core Tools, just to stay in the same window. If you open it, you will start in the folder of the solution file. To use the EF Core tools, you’d normally change into the correct folder: in this case &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Provisioning.DataLibary&lt;/code&gt; ad then run the tools, like the migration actions:&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;cd&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Provisioning.DataLibrary&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dotnet&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ef&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;migrations&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;InitialCreate&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will give you the error above: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Provisioning.DataLibrary&lt;/code&gt; is a .NET Standard library and the tools cannot analyze this.&lt;/p&gt;

&lt;h2 id=&quot;correct-commands&quot;&gt;Correct commands&lt;/h2&gt;
&lt;p&gt;To get the EF Core tools to work, you can stay in the main (solution) folder and indicate everything the tools need to find the correct references:&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;dotnet&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ef&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;migrations&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;InitialCreate&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--project&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Provisioning.DataLibrary&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--startup-project&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Provisioning.ConsoleApp&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So, with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--project Provisioning.DataLibrary&lt;/code&gt; it knows where it needs to create the migrations + folders for it.
And with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--project Provisioning.ConsoleApp&lt;/code&gt; it can find a .NET Core project to target.&lt;/p&gt;

&lt;p&gt;In the end it’s not that complicated, but certainly not intuitive.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Azure Active Directory Authentication Issue with .NET Core</title>
			<link href="https://devopsjournal.io/blog/2020/04/23/Azure-Active-Directory-Auth-Issue-NET-core"/>
			<updated>2020-04-23T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2020/04/23/Azure-Active-Directory-Auth-Issue-NET-core</id>
			<content type="html">&lt;p&gt;Today I faced an issue with Azure Active Directory authentication that was interesting enough to not this down for later reference 😁.&lt;/p&gt;

&lt;h1 id=&quot;aadsts50011-the-reply-url-specified-in-the-request-does-not-match-the-reply-urls-configured-for-the-application&quot;&gt;AADSTS50011: The reply URL specified in the request does not match the reply URLs configured for the application&lt;/h1&gt;

&lt;p&gt;I’ve got this issue in our (new) web application:
&lt;img src=&quot;/images/2020/20200423/20200423_01_Issue.png&quot; alt=&quot;Error message from Azure Active Directory&quot; /&gt;&lt;/p&gt;

&lt;p&gt;With the help from this &lt;a href=&quot;https://www.koskila.net/aadsts50011-the-reply-address-does-not-match-the-reply-addresses-configured/&quot;&gt;blogpost&lt;/a&gt; from Antti I’ve learned that the url you’ve entered to redirect to after the authentication is done, has to match &lt;strong&gt;exactly&lt;/strong&gt; with the URL you send in with the Authentication Request itself.&lt;/p&gt;

&lt;p&gt;To verify your own setup, go to the App Registration Setup and find the URL you are using.
&lt;img src=&quot;/images/2020/20200423/20200423_02_AppRegistration.png&quot; alt=&quot;Error message from Azure Active Directory&quot; /&gt;
I my case, we where using OpenIdConnect middleware that listens on a specific url for the callback that you can specify yourself (so you can match it with the App Registration). To make it clear where we are coming from, I’m using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;signin-microsoft&lt;/code&gt;.&lt;/p&gt;

&lt;h1 id=&quot;openidconnect-net-core-middleware&quot;&gt;OpenIdConnect .NET Core middleware&lt;/h1&gt;
&lt;p&gt;In the image below you can find the place where we configure this callback path (we load it from the configuration here). Do note that the middleware doesn’t want the root path here, so &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/signin-microsoft&lt;/code&gt; will do the trick in this case.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20200423/20200423_03_NET_Core_Settings.png&quot; alt=&quot;Configuration settings in .NET Core&quot; /&gt;&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Azure DevOps: Update release variables across stages</title>
			<link href="https://devopsjournal.io/blog/2020/04/17/Azure-DevOps-update-release-variable-across-stages"/>
			<updated>2020-04-17T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2020/04/17/Azure-DevOps-update-release-variable-across-stages</id>
			<content type="html">&lt;p&gt;In Azure DevOps I needed to determine a variable in one deployment stage, and use it in another. I remember finding and implementing this solution before but couldn’t figure out how I did things, so this post is for me to find it easier next time 😉.&lt;/p&gt;

&lt;p&gt;For example, in a stage I want to set a variable with the name &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ReleaseVariableName&lt;/code&gt; to a value. Searching online points you to an example on how to do this with for example the PowerShell command below. You first create a variable in the variable tab and then set/overwrite its value:&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;Write-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;##vso[task.setvariable variable=ReleaseVariableName;]Value1.0&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h4 id=&quot;note-that-you-dont-necessarily-need-to-create-variable-azure-devops-does-that-for-you-but-it-helps-in-figuring-out-what-is-happening-later-on&quot;&gt;Note that you don’t necessarily need to create variable, Azure DevOps does that for you. But it helps in figuring out what is happening later on.&lt;/h4&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20200417/melissa-guzzetta-IYh4J2zp4sk-unsplash.jpg&quot; alt=&quot;Image of sandlike waves&quot; /&gt;&lt;/p&gt;
&lt;h5 id=&quot;unsplash-logophoto-from-melissa-guzzetta&quot;&gt;&lt;a style=&quot;background-color:black;color:white;text-decoration:none;padding:4px 6px;font-family:-apple-system, BlinkMacSystemFont, &amp;quot;San Francisco&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Ubuntu, Roboto, Noto, &amp;quot;Segoe UI&amp;quot;, Arial, sans-serif;font-size:12px;font-weight:bold;line-height:1.2;display:inline-block;border-radius:3px&quot; href=&quot;https://unsplash.com/@mguzz?utm_medium=referral&amp;amp;utm_campaign=photographer-credit&amp;amp;utm_content=creditBadge&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; title=&quot;Photo from Melissa Guzzetta&quot;&gt;&lt;span style=&quot;display:inline-block;padding:2px 3px&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; style=&quot;height:12px;width:auto;position:relative;vertical-align:middle;top:-2px;fill:white&quot; viewBox=&quot;0 0 32 32&quot;&gt;&lt;title&gt;unsplash-logo&lt;/title&gt;&lt;path d=&quot;M10 9V0h12v9H10zm12 5h10v18H0V14h10v9h12v-9z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;&lt;span style=&quot;display:inline-block;padding:2px 3px&quot;&gt;Photo from Melissa Guzzetta&lt;/span&gt;&lt;/a&gt;&lt;/h5&gt;

&lt;h2 id=&quot;the-issue&quot;&gt;The issue&lt;/h2&gt;
&lt;p&gt;Testing the code above will prove that this works, but that the variable values are reset in a new Agent Job or another Stage. This stems from the fact that each job or stage can be run on a different Agent (and even in parallel) and that the values are not synced across.&lt;/p&gt;

&lt;h1 id=&quot;the-fix-use-the-rest-api-for-azure-devops&quot;&gt;The fix: use the REST API for Azure DevOps&lt;/h1&gt;
&lt;p&gt;The only way I found to update the variable value is to use the REST API for Azure DevOps, find the current release we’re in and then overwrite the variable value there. Then the next Stage / Job will pick up the new value and you can continue.&lt;/p&gt;

&lt;h5 id=&quot;do-note-that-this-updated-value-will-not-be-available-with-this-in-the-same-stage-as-youre-updating-it-in-handle-that-separately&quot;&gt;Do note that this updated value will not be available with this in the &lt;em&gt;same stage&lt;/em&gt; as you’re updating it in! Handle that separately.&lt;/h5&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#region variables&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$ReleaseVariableName&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;StageVar&apos;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$releaseurl&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;{0}{1}/_apis/release/releases/{2}?api-version=5.0&apos;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;SYSTEM_TEAMFOUNDATIONSERVERURI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;SYSTEM_TEAMPROJECTID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;RELEASE_RELEASEID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;#endregion&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;#region Get Release Definition&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;URL: &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$releaseurl&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$Release&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Invoke-RestMethod&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Uri&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$releaseurl&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Headers&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;@{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Authorization&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Bearer &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;SYSTEM_ACCESSTOKEN&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;#endregion&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;#region Output current Release Pipeline&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Output&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;Release Pipeline variables output: {0}&apos;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$Release&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;variables&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ConvertTo-Json&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Depth&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;#endregion&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;#region Update StageVar with new value&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Updating release variable with name [&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ReleaseVariableName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ReleaseVariableValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$release&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.variables.&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ReleaseVariableName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ReleaseVariableValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;
#endregion

#region update release pipeline
Write-Output (&apos;Updating Release Definition&apos;)
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$json&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; = @(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$release&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;) | ConvertTo-Json -Depth 99
Invoke-RestMethod -Uri &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$releaseurl&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; -Method Put -Body &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$json&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; -ContentType &quot;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;application/json&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot; -Headers @{Authorization = &quot;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Bearer&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;SYSTEM_ACCESSTOKEN&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot; }
#endregion

#region Get updated Release Definition
Write-Output (&apos;Get updated Release Definition&apos;)
Write-Host &quot;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;URL:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$releaseurl&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$Release&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; = Invoke-RestMethod -Uri &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$releaseurl&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; -Headers @{
    Authorization = &quot;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Bearer&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;SYSTEM_ACCESSTOKEN&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;
}
#endregion

#region Output Updated Release Pipeline
Write-Output (&apos;Updated Release Pipeline variables output: {0}&apos; -f &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$Release&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;variables&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ConvertTo-Json&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Depth&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;
#endregion
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Note&lt;/em&gt;: you will need to set the Job you are running this in to have access to the OAuth Access Token:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20200417/20200417_01_OAuthToken.png&quot; alt=&quot;OAuth Setting&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;even-easier-download-the-task-group-definition&quot;&gt;Even easier, download the Task Group definition&lt;/h2&gt;
&lt;p&gt;Making implementing this even easier, you can download my exported Task Group &lt;a href=&quot;/images/2020/20200417/rajbos%20-%20Update%20Release%20Variable%20value%20across%20stages.json&quot;&gt;here&lt;/a&gt; and import it (after reviewing it for security issues of course!) into your own environment.&lt;/p&gt;

&lt;h3 id=&quot;authorization-for-the-service-account-you-are-using&quot;&gt;Authorization for the Service account you are using&lt;/h3&gt;
&lt;p&gt;Good to note that you need &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Manage Releases&lt;/code&gt; with the service you are running the deployment pipeline with, otherwise you will run into an error like this:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;VS402904: Access denied: User Project Collection Build Service (AzDoServiceAccountName) does not have manage releases permission. Contact your  release manager.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;future-azure-devops-update&quot;&gt;Future Azure DevOps update&lt;/h2&gt;
&lt;p&gt;There is a new update for Azure DevOps on its way to make this even easier as noted by the Azure DevOps team &lt;a href=&quot;https://github.com/microsoft/azure-pipelines-tasks/issues/4743#issuecomment-614721900&quot;&gt;here&lt;/a&gt;. You can see that the initial issues was created in 2017 and the solution is rolling out in 2020 😄.&lt;/p&gt;

&lt;h1 id=&quot;update-for-yaml-pipelines&quot;&gt;Update for yaml pipelines&lt;/h1&gt;
&lt;p&gt;After reading this blog post, Sebastian Schütze knew about another way to fix this issue in a yaml pipeline: you have the option there to upload an artefact from the pipeline that can be downloaded in any subsequent stage/job.&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Just created a new blog post: Variables Cross Stage in Azure DevOps with YAML &lt;a href=&quot;https://twitter.com/hashtag/AzureDevOps?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#AzureDevOps&lt;/a&gt; &lt;a href=&quot;https://twitter.com/hashtag/DevOps?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#DevOps&lt;/a&gt; &lt;a href=&quot;https://twitter.com/hashtag/AzurePipelines?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#AzurePipelines&lt;/a&gt; &lt;a href=&quot;https://twitter.com/hashtag/AzureDevOps?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#AzureDevOps&lt;/a&gt; &lt;a href=&quot;https://twitter.com/hashtag/Cross?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#Cross&lt;/a&gt;-Stage &lt;a href=&quot;https://twitter.com/hashtag/Variables?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#Variables&lt;/a&gt; &lt;a href=&quot;https://twitter.com/hashtag/YAML?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#YAML&lt;/a&gt; AzureDevOps AzureDevOps &lt;a href=&quot;https://t.co/CI94l1K8yG&quot;&gt;https://t.co/CI94l1K8yG&lt;/a&gt;&lt;/p&gt;&amp;mdash; Sebastian Schütze 🚀☁️ (@RazorSPoint) &lt;a href=&quot;https://twitter.com/RazorSPoint/status/1251537984366743553?ref_src=twsrc%5Etfw&quot;&gt;April 18, 2020&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;p&gt;You can read his post here: &lt;a href=&quot;https://www.razorspoint.com/2020/2020/04/18/variables-cross-stage-in-azure-devops-with-yaml/&quot;&gt;www.razorspoint.com&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;recreation-in-classic-pipeline&quot;&gt;Recreation in Classic Pipeline&lt;/h2&gt;
&lt;p&gt;I wanted to check to see if I could replicate the behavior in a classic pipeline and it all seemed good: there is a Publish Pipeline Artifact task available that is meant just for cases like this.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20200417/20200417_02_PublishPipelineArtefact.png&quot; alt=&quot;Screenshot of Publish pipeline artifact&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/utility/publish-pipeline-artifact?view=azure-devops&amp;amp;viewFallbackFrom=vsts&quot;&gt;docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can then retrieve the file in the next stage/job and read it back in…. Or so was the plan:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20200417/20200417_03_ReadPublishedPipelineArtefact.png&quot; alt=&quot;Screenshot of Publish pipeline artifact&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20200417/20200417_04_ErrorReadingPublishedPipelineArtefact.png&quot; alt=&quot;Screenshot of Publish pipeline artifact&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The Upload Artifact task cannot be run in a release pipeline! 😠💩
It has been added to the &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/utility/publish-pipeline-artifact?view=azure-devops&amp;amp;viewFallbackFrom=vsts&quot;&gt;documentation&lt;/a&gt;, but why they then show the task as being available and all, is beyond me. There have been more people who want this to work, as you can find in this &lt;a href=&quot;https://github.com/Microsoft/azure-pipelines-tasks/issues/8812&quot;&gt;GitHub issue&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There is an option to upload a file to the release pipeline, but then you cannot download it again:&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;Write-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;##vso[task.uploadfile]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$file&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;FullName&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h5 id=&quot;note-you-can-then-download-this-file-with-the-logs-for-the-release-pipeline&quot;&gt;Note: you can then download this file with the logs for the release pipeline.&lt;/h5&gt;
</content>
		</entry>
	
		<entry>
			<title>Install an Azure DevOps Agent behind a proxy</title>
			<link href="https://devopsjournal.io/blog/2020/04/16/Run-Azure-DevOps-Agent-Behind-a-proxy"/>
			<updated>2020-04-16T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2020/04/16/Run-Azure-DevOps-Agent-Behind-a-proxy</id>
			<content type="html">&lt;p&gt;Sometimes you need to run the Azure DevOps Agent behind a proxy. If you search around you can find a lot of posts regarding this, and I wanted to have my own overview of all the things you need to keep in mind. At least I’ve tested this list myself 😁.&lt;/p&gt;

&lt;p&gt;To run the Azure DevOps agent behind a proxy, the proxy must be updated with the url’s below in the allow-list. The origination of this list comes from &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/organizations/security/allow-list-ip-url?view=azure-devops&amp;amp;WT.mc_id=AZ-MVP-5003719&quot;&gt;Microsoft&lt;/a&gt; and &lt;a href=&quot;https://jessehouwing.net/azure-devops-what-domains-are-used-by-your-account/&quot;&gt;Jesse Houwing&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id=&quot;proxy-allow-list&quot;&gt;Proxy allow-list&lt;/h1&gt;
&lt;p&gt;To be able to install the agent we have requested these url’s to be added to the allow-list:&lt;/p&gt;

&lt;h3 id=&quot;organization-specific&quot;&gt;Organization specific:&lt;/h3&gt;
&lt;!-- markdown-link-check-disable --&gt;
&lt;p&gt;Replace ORGANIZATIONNAME with the name of your organization of course.&lt;/p&gt;

&lt;p&gt;https://ORGANIZATIONNAME.visualstudio.com&lt;br /&gt;
https://ORGANIZATIONNAME.vsrm.visualstudio.com&lt;br /&gt;
https://ORGANIZATIONNAME.pkgs.visualstudio.com&lt;br /&gt;
https://ORGANIZATIONNAME.vssps.visualstudio.com&lt;br /&gt;
http://sso.ORGANIZATIONNAME.com&lt;br /&gt;
https://sso.ORGANIZATIONNAME.com&lt;br /&gt;
https://login.ORGANIZATIONNAME.com&lt;/p&gt;

&lt;h3 id=&quot;generic&quot;&gt;Generic&lt;/h3&gt;
&lt;p&gt;https://login.microsoftonline.com&lt;br /&gt;
https://app.vssps.visualstudio.com&lt;br /&gt;
https://login.live.com&lt;br /&gt;
https://auth.gfx.ms&lt;br /&gt;
https://app.vsspsext.visualstudio.com&lt;br /&gt;
https://*.ods.opinsights.azure.com&lt;br /&gt;
https://*.oms.opinsights.azure.com&lt;br /&gt;
https://ods.systemcenteradvisor.com&lt;br /&gt;
https://secure.aadcdn.microsoftonline-p.com&lt;br /&gt;
https://*.dev.azure.com&lt;br /&gt;
https://dev.azure.com&lt;br /&gt;
https://login.microsoftonline.com&lt;br /&gt;
https://management.core.windows.net&lt;br /&gt;
https://api.nuget.org&lt;br /&gt;
https://login.microsoftonline.com&lt;br /&gt;
https://microsoftonline.com&lt;br /&gt;
https://go.microsoft.com&lt;br /&gt;
https://microsoft.com&lt;br /&gt;
https://app.vssps.dev.azure.com&lt;br /&gt;
https://dev.azure.com&lt;br /&gt;
https://aex.dev.azure.com&lt;br /&gt;
https://vstsagentpackage.azureedge.net&lt;br /&gt;
https://graph.windows.net&lt;br /&gt;
https://aadcdn.msauth.net&lt;br /&gt;
https://aadcdn.msftauth.net&lt;br /&gt;
https://windows.net&lt;br /&gt;
https://visualstudio.com&lt;br /&gt;
https://live.com&lt;br /&gt;
https://app.vssps.visualstudio.com&lt;br /&gt;
https://cdn.vsassets.io&lt;br /&gt;
https://gallerycdn.vsassets.io&lt;br /&gt;
https://static2.sharepointonline.com&lt;br /&gt;
https://spsprodweu2.vssps.visualstudio.com&lt;br /&gt;
https://vssps.dev.azure.com&lt;br /&gt;
https://vsrm.dev.azure.com&lt;/p&gt;

&lt;h2 id=&quot;start-the-installation-by-making-it-aware-of-the-proxy&quot;&gt;Start the installation by making it aware of the proxy:&lt;/h2&gt;
&lt;p&gt;The installation will then do the rest.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.\config.cmd --proxyurl &quot;https://fqdn.url.of.your.proxy:3128&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;If there are any errors, it will be logged in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_diag&lt;/code&gt; folder.
&lt;!-- markdown-link-check-enable --&gt;&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>.NET Core: logging the configured URLs during starting of the application</title>
			<link href="https://devopsjournal.io/blog/2020/04/16/.NET-Core-logging-urls-during-starting-of-the-web-application"/>
			<updated>2020-04-16T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2020/04/16/.NET-Core-logging-urls-during-starting-of-the-web-application</id>
			<content type="html">&lt;p&gt;Sometimes you want to log information during the startup of the web application. In our case we wanted to log some generic information about the server to see where we are in the process. Doing so proved a little more complicated than expected, so I needed to document this in case I need this in the future 😉.&lt;/p&gt;

&lt;p&gt;Usually you can only find out the incoming URLs if you have a request you are handling, but during the startup phase in .NET core this is not possible. In ASP.NET you could do this with this call, but this is not available in .NET Core.&lt;/p&gt;
&lt;div class=&quot;language-c# highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;HttpRuntime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AppDomainAppVirtualPath&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;the-solution&quot;&gt;The solution&lt;/h1&gt;
&lt;p&gt;Below is the full setup we started using. We are using &lt;a href=&quot;https://serilog.net/&quot;&gt;Serilog&lt;/a&gt; for structured logging and sending it to an ElasticSearch endpoint.
Key part is of course in the call to the ServerFeatures: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;host.ServerFeatures.Get&amp;lt;IServerAddressesFeature&amp;gt;();&lt;/code&gt;&lt;/p&gt;

&lt;h2 id=&quot;full-code-example&quot;&gt;Full code example&lt;/h2&gt;
&lt;p&gt;Note: this code was tested with .NET Core 2.2.&lt;/p&gt;

&lt;div class=&quot;language-c# highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Microsoft.AspNetCore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Microsoft.AspNetCore.Hosting&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Microsoft.AspNetCore.Hosting.Server.Features&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Logger&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;LoggerConfiguration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ReadFrom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ConfigurationService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Enrich&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WithElasticApmCorrelationInfo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Information&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;Starting {assembly} on {environment}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;Assembly&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;GetCallingAssembly&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;GetName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;Environment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ASPNETCORE_ENVIRONMENT&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;host&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CreateWebHostBuilder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;webAddresses&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ServerFeatures&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IServerAddressesFeature&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;foreach&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;webAddress&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;webAddresses&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Addresses&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Information&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;WebAddress: {webAddress}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;webAddress&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Fatal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Application start-up failed&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;finally&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CloseAndFlush&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
		</entry>
	
		<entry>
			<title>Nesting .NET Core appsettings.json</title>
			<link href="https://devopsjournal.io/blog/2020/04/05/netcore-nested-appsettings-json"/>
			<updated>2020-04-05T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2020/04/05/netcore-nested-appsettings-json</id>
			<content type="html">&lt;p&gt;I was working on a new .NET Core Unit/Integration Test project in a solution using Visual Studio and need to load some setting from the configuration. Naturally I wanted to use the same setup for retrieving those settings as in the real project, so I added a new file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;appsettings.json&lt;/code&gt;. Next up I wanted to add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;appsettings.Development.json&lt;/code&gt; just like we use in normal projects. Somehow I expected it to be nested beneath &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;appsettings.json&lt;/code&gt;, like in the normal project. Of course, it didn’t 😄. While searching for a solution I noticed a lot of screenshots with the same issue: the files where not nested. Here is how to fix it.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20200405/20200405_01_Files.png&quot; alt=&quot;Screenshot of files placed below each other instead of nested&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-solution&quot;&gt;The solution&lt;/h2&gt;
&lt;p&gt;The solution is updating your csproj file with a (new) item group that indicates the behavior you want:&lt;/p&gt;
&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;nt&quot;&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;None&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Update=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;appsettings.Development.json&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;DependentUpon&amp;gt;&lt;/span&gt;appsettings.json&lt;span class=&quot;nt&quot;&gt;&amp;lt;/DependentUpon&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/None&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With this you tell Visual Studio how you want this file to be displayed!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20200405/20200405_02_Fixed.png&quot; alt=&quot;Fixed layout&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;visual-studio-code&quot;&gt;Visual Studio Code&lt;/h1&gt;
&lt;p&gt;Note: unfortunately the solution above will &lt;strong&gt;not&lt;/strong&gt; work for Visual Studio code!
For adding the same setup in Visual Studio Code you need to add the following to your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.vscode/settings.json&lt;/code&gt; file:&lt;/p&gt;
&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nl&quot;&gt;&quot;explorer.fileNesting.enabled&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;explorer.fileNesting.patterns&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;appsettings.json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;appSettings.*.json&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
		</entry>
	
		<entry>
			<title>Deploy local IIS application on Windows with Azure DevOps</title>
			<link href="https://devopsjournal.io/blog/2020/03/29/Deploy-locally-on-Windows-Azure-DevOps"/>
			<updated>2020-03-29T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2020/03/29/Deploy-locally-on-Windows-Azure-DevOps</id>
			<content type="html">&lt;p&gt;Sometimes you need to deploy an application on a machine, but there is no option to use &lt;a href=&quot;https://docs.microsoft.com/en-us/powershell/scripting/learn/remoting/running-remote-commands?view=powershell-7.1&amp;amp;WT.mc_id=DOP-MVP-5003719&quot;&gt;PowerShell remoting&lt;/a&gt; from the outside. In that case, you can deploy an Azure DevOps agent on that machine and use that for local deployments.&lt;/p&gt;

&lt;p&gt;These are the steps to deploy an application with Azure DevOps on the localhost of the agent. As an example I’m using IIS to deploy a web application to for this.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20200329/alex-holyoake-PmzdQjCCPws-unsplash.jpg&quot; alt=&quot;Image of a light bulb&quot; /&gt;&lt;/p&gt;
&lt;h5 id=&quot;unsplash-logoalex-holyoake&quot;&gt;&lt;a style=&quot;background-color:black;color:white;text-decoration:none;padding:4px 6px;font-family:-apple-system, BlinkMacSystemFont, &amp;quot;San Francisco&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Ubuntu, Roboto, Noto, &amp;quot;Segoe UI&amp;quot;, Arial, sans-serif;font-size:12px;font-weight:bold;line-height:1.2;display:inline-block;border-radius:3px&quot; href=&quot;https://unsplash.com/@stairhopper?utm_medium=referral&amp;amp;utm_campaign=photographer-credit&amp;amp;utm_content=creditBadge&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; title=&quot;Photo by Alex Holyoake&quot;&gt;&lt;span style=&quot;display:inline-block;padding:2px 3px&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; style=&quot;height:12px;width:auto;position:relative;vertical-align:middle;top:-2px;fill:white&quot; viewBox=&quot;0 0 32 32&quot;&gt;&lt;title&gt;unsplash-logo&lt;/title&gt;&lt;path d=&quot;M10 9V0h12v9H10zm12 5h10v18H0V14h10v9h12v-9z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;&lt;span style=&quot;display:inline-block;padding:2px 3px&quot;&gt;Alex Holyoake&lt;/span&gt;&lt;/a&gt;&lt;/h5&gt;

&lt;h2 id=&quot;local-windows-user&quot;&gt;Local Windows User&lt;/h2&gt;
&lt;p&gt;First, create a local windows user to run the agent with. We need to give the account some extra rights later on, and we should not do that with the default service account the Azure DevOps Agent uses.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;We need a Windows user for the agent to run with&lt;/li&gt;
  &lt;li&gt;Make a local service user to run the agent with&lt;/li&gt;
  &lt;li&gt;Make sure it has run as service rights (use the Group Policy Editor with gpoedit.msc in the ‘Run’ command)&lt;/li&gt;
  &lt;li&gt;Store the password for the user somewhere safe.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;iis-deployments-specific&quot;&gt;IIS deployments specific&lt;/h2&gt;
&lt;p&gt;The user needs to be added to the local Administrator group to be able to execute &lt;strong&gt;AppExec&lt;/strong&gt; commands, used for administrative tasks in IIS, like creating a website. I’ve checked, but couldn’t find better ways to do this with a less privileged account.&lt;/p&gt;

&lt;h2 id=&quot;windows-remote-management-winrm&quot;&gt;Windows Remote Management (WinRM)&lt;/h2&gt;
&lt;p&gt;The default &lt;a href=&quot;https://github.com/microsoft/azure-pipelines-extensions/blob/master/Extensions/IISWebAppDeploy/Src/Tasks/IISWebAppMgmt/IISWebAppMgmtV1/README_IISAppMgmt.md&quot;&gt;deploy task&lt;/a&gt; in Azure DevOps use PowerShell with remote management to do the administrative tasks through &lt;a href=&quot;https://docs.microsoft.com/en-us/iis/get-started/getting-started-with-iis/getting-started-with-appcmdexe?WT.mc_id=DOP-MVP-5003719&quot;&gt;AppCmd&lt;/a&gt;. You can enable to use this from a remote host, but you can also use this on the local host!&lt;/p&gt;

&lt;h4 id=&quot;check-locally-trusted-hosts&quot;&gt;Check locally trusted hosts:&lt;/h4&gt;
&lt;p&gt;Check the existing trusted hosts to see if the local host is already in the list:
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Get-Item WSMan:\localhost\Client\TrustedHosts&lt;/code&gt;
By default it is not added to the list.&lt;/p&gt;

&lt;h4 id=&quot;set-them-open-for-localhost-and-a-specific-dns-name&quot;&gt;Set them open for localhost and a specific DNS name&lt;/h4&gt;
&lt;p&gt;If you don’t find the localhost in the trusted list, you’ll need to add them:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Set-Item WSMan:\localhost\Client\TrustedHosts -Value 127.0.0.1
Set-Item WSMan:\localhost\Client\TrustedHosts -Value servername.FQDNdomain.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;I add them with both the local IP-address and the fully qualified domain name so it will work with both, so if we later do get an option to use it from the outside, it will still work.&lt;/p&gt;

&lt;h3 id=&quot;test-winrm-sessions&quot;&gt;Test WinRM sessions&lt;/h3&gt;
&lt;p&gt;You can then test the connectivity with the created user with a PowerShell script (run it on the VM).&lt;/p&gt;

&lt;h4 id=&quot;test-the-session&quot;&gt;Test the session&lt;/h4&gt;
&lt;p&gt;Get credential from a windows popup and set up a session:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$USER = Get-Credential
Enter-PSSession -ComputerName 127.0.0.1 -Credential $USER
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Or you can skip the popup for easier testing, remove the password from the file when done!&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$Username = &apos;\AzurePipelinesSVC&apos;
$Password = &apos;1234512345&apos;
$pass = ConvertTo-SecureString -AsPlainText $Password -Force

$SecureString = $pass
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And create a session with the user and credentials you’ve stored:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$USER = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $Username, $SecureString

# Entering the session, after that you can for example use $env:computername to see where that session is opened.
Enter-PSSession -ComputerName 127.0.0.1 -Credential $USER
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
		</entry>
	
		<entry>
			<title>Oops, you&apos;ve worked on the master branch and cannot push to the remote</title>
			<link href="https://devopsjournal.io/blog/2020/03/26/oops-commits-on-protected-master-branch"/>
			<updated>2020-03-26T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2020/03/26/oops-commits-on-protected-master-branch</id>
			<content type="html">&lt;p&gt;We’ve all been there, what happens when you commit changes to the master branch and during the sync to the remote you get an error.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20200326/20200326_02_VisualStudio_Error.png&quot; alt=&quot;Screenshot of Visual Studio Error&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Starting point: you didn’t check the branch you where committing to:
So during the sync you get an error:
&lt;img src=&quot;/images/2020/20200326/20200326_01_VisualStudio_Cause.png&quot; alt=&quot;Screenshot of the cause in Visual Studio&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In the output window this message is shown:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Pushing to https://**********.visualstudio.com/DefaultCollection/CICD/_git/DemoRepo
To https://**********.visualstudio.com/DefaultCollection/CICD/_git/DemoRepo
Error: failed to push some refs to &apos;https://**********.visualstudio.com/DefaultCollection/CICD/_git/DemoRepo&apos;
Error encountered while pushing to the remote repository: rejected master -&amp;gt; master (TF402455: Pushes to this branch are not permitted; you must use a pull request to update this branch.)
This means the remote branch is protected and that you can only update it via a Pull Request.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You changes will not be lost! Here’s what to do:
&lt;strong&gt;Step 1.&lt;/strong&gt; Move your changes to a new branch, by creating a new branch from here:
 (note the master branch as the source)!
&lt;img src=&quot;/images/2020/20200326/20200326_03_VisualStudio_CreateNewBranch.png&quot; alt=&quot;Screenshot of creating the new branch in Visual Studio&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;now-you-can-push-that-branch-to-the-remote-and-create-a-pull-request-with-you-changes&quot;&gt;Now you can push that branch to the remote and create a Pull Request with you changes.&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Step 2.&lt;/strong&gt; If you switch back to the master branch, you’ll still see those commits on it and you still cannot push…&lt;/p&gt;

&lt;p&gt;We need to reset the commit from the master branch. There is no way to do this with Visual Studio, so we need to use the command line for that.&lt;/p&gt;

&lt;p&gt;Open a command line and go to your repository folder. I’m using Windows Terminal here.&lt;/p&gt;

&lt;p&gt;Run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git status&lt;/code&gt; in the folder to check if you are in the correct location and state:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20200326/20200326_03_CommandLine_Reset.png&quot; alt=&quot;Screenshot of creating the new branch in Visual Studio&quot; /&gt;&lt;/p&gt;

&lt;p&gt;To reset the last N commits, use this command: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git reset --hard HEAD^N&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;As an example: to reset the last 2 commits: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git reset --hard HEAD^2&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Be aware:&lt;/strong&gt; this is a destructive operation: data from the master branch in those commits will be lost! That’s why we saved them to another branch.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Azure SQL Database Firewall</title>
			<link href="https://devopsjournal.io/blog/2020/02/12/Azure-SQL-Database-Firewall"/>
			<updated>2020-02-12T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2020/02/12/Azure-SQL-Database-Firewall</id>
			<content type="html">&lt;p&gt;Did you know you have more than one option to set the SQL firewall settings on an Azure SQL Database? Most people know you can set firewall rules on the &lt;strong&gt;server&lt;/strong&gt; level:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20200212/20200212_01_ServerFirewallRules.png&quot; alt=&quot;Firewall rules on the database level&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;example-settings&quot;&gt;Example settings&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2020/20200212/20200212_01_ServerFirewallRulesExample.png&quot; alt=&quot;Example of Firewall rules on the server level&quot; /&gt;&lt;/p&gt;

&lt;h5 id=&quot;do-note-that-toggling-the-setting-for-allow-azure-services-and-resources-to-access-this-server-opens-up-connection-from-anywhere-inside-of-the-azure-cloud-this-is-not-limited-to-your-own-subscription&quot;&gt;Do note that toggling the setting for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Allow Azure services and resources to access this server&lt;/code&gt; opens up connection from &lt;strong&gt;anywhere&lt;/strong&gt; inside of the Azure Cloud: this is not limited to your own subscription!&lt;/h5&gt;

&lt;h1 id=&quot;firewall-settings-on-the-database-level&quot;&gt;Firewall settings on the database level&lt;/h1&gt;
&lt;p&gt;There is one extra option, which is very helpful if you want more control per database, instead of opening the firewall for all databases on the same server.&lt;/p&gt;

&lt;p&gt;You can set the firewall rules on the database level as well! These will be checked after the server level settings, so you can mix and match.&lt;/p&gt;

&lt;p&gt;Do note that the server level settings are evaluated &lt;strong&gt;after&lt;/strong&gt; the database level settings have been checked. So if you have deleted an IP-address from the database level but it is still active on the server level, the IP-address can still access the database. See the docs &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-sql/database/firewall-configure#server-level-versus-database-level-ip-firewall-rules?WT.mc_id=AZ-MVP-5003719&quot;&gt;here&lt;/a&gt; for more information.&lt;/p&gt;

&lt;p&gt;There is no editor available, you’ll need to edit the settings in the database itself, for example with the Query editor or through a query or table editor with the SQL Server Management Studio or Azure Data Studio
&lt;img src=&quot;/images/2020/20200212/20200212_02_DatabaseFirewallRules.png&quot; alt=&quot;Firewall rules on the database level&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You can use the table &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sys.database_firewall_rules&lt;/code&gt; to find the current settings and then use the stored procedures
&lt;a href=&quot;https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-set-database-firewall-rule-azure-sql-database?view=azuresqldb-current&amp;amp;WT.mc_id=AZ-MVP-5003719&quot;&gt;sp_set_database_firewall_rule&lt;/a&gt; to set a rule or &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-delete-database-firewall-rule-azure-sql-database?view=azuresqldb-current?WT.mc_id=AZ-MVP-5003719&quot;&gt;sp_delete_database_firewall_rule&lt;/a&gt; to delete them.&lt;/p&gt;

&lt;h2 id=&quot;opening-up-the-connection-for-azure-cloud-resources&quot;&gt;Opening up the connection for Azure Cloud resources&lt;/h2&gt;
&lt;p&gt;To open the connection from other Azure resources (note: not limited to &lt;strong&gt;your&lt;/strong&gt; subscriptions), you can toggle the setting for IP-address &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0.0.0.0&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;other-options&quot;&gt;Other options&lt;/h3&gt;
&lt;p&gt;You can set the server level firewall rules with the &lt;a href=&quot;https://docs.microsoft.com/en-us/cli/azure/sql/server/firewall-rule?view=azure-cli-latest&amp;amp;WT.mc_id=AZ-MVP-5003719&quot;&gt;Azure CLI&lt;/a&gt;, with a login on the &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-set-firewall-rule-azure-sql-database?view=azuresqldb-current&amp;amp;WT.mc_id=AZ-MVP-5003719&quot;&gt;master database&lt;/a&gt; or through &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/sql-database/sql-database-firewall-configure#server-level-versus-database-level-ip-firewall-rules?WT.mc_id=AZ-MVP-5003719&quot;&gt;PowerShell&lt;/a&gt;.
For database level, your only option is the SQL statements.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Using CD with Application Insights Status Monitor on IIS</title>
			<link href="https://devopsjournal.io/blog/2020/01/27/Using-CD-with-Application-Insights-Status-Monitor-on-IIS"/>
			<updated>2020-01-27T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2020/01/27/Using-CD-with-Application-Insights-Status-Monitor-on-IIS</id>
			<content type="html">&lt;p&gt;Recently I found an IIS hosted web application that we couldn’t instrument with Application Insights. As it is running in IIS,
it is possible to start monitoring it with Application Insights through the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Web platform Installer&lt;/code&gt;. You can find how to do that
for example &lt;a href=&quot;https://www.c-sharpcorner.com/article/configure-an-iis-server-to-use-application-insight/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Installation and configuration is rather straightforward.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20200127/dominik-schroder-FIKD9t5_5zQ-unsplash.jpg&quot; alt=&quot;Photo of serene clouds&quot; /&gt;&lt;/p&gt;
&lt;h5 id=&quot;--unsplash-logo-photo-by-dominik-schröder&quot;&gt;&lt;a style=&quot;background-color:black;color:white;text-decoration:none;padding:4px 6px;font-family:-apple-system, BlinkMacSystemFont, &amp;quot;San Francisco&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Ubuntu, Roboto, Noto, &amp;quot;Segoe UI&amp;quot;, Arial, sans-serif;font-size:12px;font-weight:bold;line-height:1.2;display:inline-block;border-radius:3px&quot; href=&quot;https://unsplash.com/@wirhabenzeit?utm_medium=referral&amp;amp;utm_campaign=photographer-credit&amp;amp; utm_content=creditBadge&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; title=&quot;Photo by Dominik Schröder&quot;&gt; &lt;span style=&quot;display:inline-block;padding:2px 3px&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; style=&quot;height:12px;width:auto;position:relative;vertical-align:middle;top:-2px;fill:white&quot; viewBox=&quot;0 0 32 32&quot;&gt; &lt;title&gt;unsplash-logo&lt;/title&gt;&lt;path d=&quot;M10 9V0h12v9H10zm12 5h10v18H0V14h10v9h12v-9z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt; &lt;span style=&quot;display:inline-block;padding:2px 3px&quot;&gt;Photo by Dominik Schröder&lt;/span&gt;&lt;/a&gt;&lt;/h5&gt;

&lt;p&gt;What this post is about, is the files you need to take care of when you are deploying your web application with a Continuous Deployment
pipeline, as we where doing. In that case you’ll find, that the connection with the Application Insights instance keeps getting lost.&lt;/p&gt;

&lt;p&gt;Figuring out what was happening took some time and meticulously checking before and after of directories and file contents.&lt;/p&gt;

&lt;h1 id=&quot;solution-for-the-disconnect&quot;&gt;Solution for the disconnect&lt;/h1&gt;
&lt;p&gt;After taking care of the default settings in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ApplicationInsights.config&lt;/code&gt; (instrumentation key) and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web.config&lt;/code&gt; (handlers),
we found out that we needed to include the binaries in the bin folder that the Status Monitor sneakily added to our application directory:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20200127/20200122_binaries.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;After adding these files (for example in an internal NuGet package) and including them during our deployment, the Status Monitor now stays
connected and sending telemetry to you Application Insights instance.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Azure Repos Policy Settings Issue</title>
			<link href="https://devopsjournal.io/blog/2020/01/21/Azure-Repos-Policy-Settings-Issue"/>
			<updated>2020-01-21T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2020/01/21/Azure-Repos-Policy-Settings-Issue</id>
			<content type="html">&lt;p&gt;I ran into an issue where I could not change Azure Repositories Policy settings, even while my account was in the the Project Administrator group. Somehow I could set the policy, but not change it later on! This posts explains how I got here and how I finally found out why this happened. In the end it was a simple fix, only rather difficult to find the reason behind this 😄.&lt;/p&gt;

&lt;h2 id=&quot;adding-an-email-validation-policy&quot;&gt;Adding an email validation policy&lt;/h2&gt;
&lt;p&gt;It all started at a new organization where I wanted to make sure that everyone was pushing changes into the repositories connected with their work e-mail address. To do that, you can set up a ‘Commit author email validation’ policy on each repository. That can be a lot of work if you have a lot of repo’s 😀. Fortunately for us, a feature has been added to set those policies on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;project level&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&quot;side-note-you-can-even-set-those-policies-on-the-company-level-but-only-with-the-api&quot;&gt;Side note: you can even set those policies on the company level, but only with the &lt;a href=&quot;https://jessehouwing.net/azure-repos-git-configuring-standard-policies-on-repositories/&quot;&gt;api&lt;/a&gt;.&lt;/h4&gt;
&lt;h4 id=&quot;side-note-you-can-have-multiple-patterns-here-as-well-just-split-them-with-a-semicolon-in-between&quot;&gt;Side note: you can have multiple patterns here as well, just split them with a semicolon in between.&lt;/h4&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20200121/20200121_AddingPolicy.png&quot; alt=&quot;Adding a policy&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You can see here, that I have set a policy on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Git repositories&lt;/code&gt; level, easy as that. From now on, everyone that tries to push something into any of these repositories with an email that doesn’t match this policy, will get an error telling them to fix the setting.&lt;/p&gt;
&lt;h4 id=&quot;side-note-find-out-how-to-setup-the-commit-email-address-from-the-git-documentation&quot;&gt;Side note: find out how to setup the commit email address from the &lt;a href=&quot;https://git-scm.com/docs/user-manual#telling-git-your-name&quot;&gt;Git documentation&lt;/a&gt;.&lt;/h4&gt;

&lt;h2 id=&quot;policy-inheritance&quot;&gt;Policy Inheritance&lt;/h2&gt;
&lt;p&gt;The tricky part for me was that these policies are now &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inherited&lt;/code&gt; by all the repositories. It’s not that clear in the UI what happens with the inheritance: are the settings from the top level extended with the settings on the lower level? I’ve seen strange things happen here. I’ll come back to that later.&lt;/p&gt;

&lt;p&gt;You can see how the inheritance looks here:
&lt;img src=&quot;/images/2020/20200121/20200121_PolicyInheritance.png&quot; alt=&quot;Policy Inheritance&quot; /&gt;
Note the highlighted area and the text.&lt;/p&gt;

&lt;h2 id=&quot;blocking-policy-editing&quot;&gt;Blocking Policy Editing&lt;/h2&gt;
&lt;p&gt;Here is the catch that took me some time to figure out. I wanted to prevent team members to change any of these policies, so that they cannot circumvent them by disabling them. (yes, there is a note to make about trusting your team to do the right thing 😄).&lt;/p&gt;

&lt;p&gt;So I created a policy that blocked members of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Contributor&lt;/code&gt; group from changing the policies. To do that, I set the rights for that group to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Deny&lt;/code&gt;.
&lt;img src=&quot;/images/2020/20200121/20200121_BlockingPolicyEditing.png&quot; alt=&quot;Blocking Policy Editing&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;result-on-the-organization-level&quot;&gt;Result on the organization level&lt;/h2&gt;
&lt;p&gt;If you go back to the e-mail policy on the top level, you can see that the message here is still the same: the setting is inherited from the project level. Except for that it is now disabled!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020/20200121/20200121_PolicyInheritanceEditingBlocked.png&quot; alt=&quot;Checking the policy&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;cause-of-the-issue&quot;&gt;Cause of the issue&lt;/h2&gt;
&lt;p&gt;In hindsight, it is clear that this is because my account is also part of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Contributors&lt;/code&gt; group. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Deny&lt;/code&gt; settings have a higher priority then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Allow&lt;/code&gt; settings! Fixing it was changing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Deny&lt;/code&gt; setting to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Not set&lt;/code&gt;: that means that the contributors cannot change the settings, but this setting is not as explicit and people with the correct rights can still update policies.&lt;/p&gt;

&lt;h1 id=&quot;policy-inheritance-questions&quot;&gt;Policy Inheritance questions&lt;/h1&gt;
&lt;p&gt;As noted before, it’s not clear if the policies are overwritten on the lower level if you set additional data on it. This is not clear from the &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/organizations/security/permissions?view=azure-devops&amp;amp;tabs=preview-page#git-repository-permissions-object-level&amp;amp;WT.mc_id=DOP-MVP-5003719&quot;&gt;documentation&lt;/a&gt;
&lt;img src=&quot;/images/2020/20200121/20200121_PolicyInheritance.png&quot; alt=&quot;Policy Inheritance&quot; /&gt;&lt;/p&gt;

&lt;p&gt;For example, you can add an extra domain to use validation. It will get stored and stays in the input if you refresh.
If you remove the initial domain but add a different one, then only that new domain is stored. Is the original rule now active? I need to test this out 😄.
&lt;img src=&quot;/images/2020/20200121/20200121_PolicyInheritanceExtend.png&quot; alt=&quot;Policy Inheritance Extended&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If you remove all text from the policy, it will show that it stored an empty value. On refresh, the policy from the top-level has been filled in again!&lt;/p&gt;
&lt;h5 id=&quot;i-suspect-that-the-entries-at-the-lower-level-is-additive-and-the-top-level-value-always-gets-verified&quot;&gt;I suspect that the entries at the lower level is additive and the top level value always gets verified.&lt;/h5&gt;
</content>
		</entry>
	
		<entry>
			<title>Update Azure CLI Extensions</title>
			<link href="https://devopsjournal.io/blog/2020/01/09/Update-Azure-CLI-extension"/>
			<updated>2020-01-09T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2020/01/09/Update-Azure-CLI-extension</id>
			<content type="html">&lt;p&gt;After getting a message that a command I wanted to use was not available in my local installation, I needed to update an Azure CLI extension. Finding that information was scattered around the internet, and took me to long to find. So for future reference, I’ll document it here 😄. Should be way easier to find next time 🙈.&lt;/p&gt;

&lt;p&gt;If you know the name of the extension, you can find it version by using the show command:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;az extension show --name azure-devops --output table
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the result you can find out the version that you have installed:
&lt;img src=&quot;/images/2020/20200109/20200109_01_ExtensionShowVersion.png&quot; alt=&quot;Powershell output of the command showing the version number&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If you don’t know the name of the extension you can list ALL extensions and try to find it.
For example I didn’t know the name of the Azure DevOps extension: the command always is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;az devops ***&lt;/code&gt;, but the extension &lt;strong&gt;name&lt;/strong&gt; is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;azure-devops&lt;/code&gt;. I understand why they kept the command name short, but you need to know how to look and where to figure out what is going on.&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;az&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;extension&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;list-available&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--output&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;table&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can then update the extension with the following command.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; az extension update --name azure-devops
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Do note, that the command will tell you nothing that displays if the update was successful or not! Apparently the decision was made to not show you any feedback during default execution. If you want more information, you can add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--verbose&lt;/code&gt; to the end of the command.&lt;/p&gt;

&lt;h3 id=&quot;updating-the-azure-cli-itself&quot;&gt;Updating the Azure CLI itself&lt;/h3&gt;
&lt;p&gt;Do note, that you cannot update the CLI itself by using any commands for it. You need to download and install the latest version from &lt;a href=&quot;https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest&amp;amp;WT.mc_id=AZ-MVP-5003719&quot;&gt;Microsoft&lt;/a&gt;.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Deploy .NET Core web application using GitHub Actions</title>
			<link href="https://devopsjournal.io/blog/2019/10/26/Deploy-dotnetcore-webapp-with-GitHub-Actions"/>
			<updated>2019-10-26T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2019/10/26/Deploy-dotnetcore-webapp-with-GitHub-Actions</id>
			<content type="html">&lt;p&gt;I wanted to try out using &lt;a href=&quot;https://github.com/features/actions&quot;&gt;GitHub Actions&lt;/a&gt; to deploy a .NET Core web application to Azure. Microsoft already has some actions available to accomplish this, so this should be rather straightforward 😄. I haven’t really played with GitHub Actions yet, so this should be rather informative 😁.
Usually I do this using &lt;a href=&quot;https://dev.azure.com&quot;&gt;Azure DevOps&lt;/a&gt;, so this will be a nice way to check the other side of the fence.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20191026/emre-karatas-Ib2e4-Qy9mQ-unsplash.jpg&quot; alt=&quot;Hero image&quot; /&gt;&lt;/p&gt;
&lt;h5 id=&quot;unsplash-logophoto-by-emre-karataş&quot;&gt;&lt;a style=&quot;background-color:black;color:white;text-decoration:none;padding:4px 6px;font-family:-apple-system, BlinkMacSystemFont, &amp;quot;San Francisco&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Ubuntu, Roboto, Noto, &amp;quot;Segoe UI&amp;quot;, Arial, sans-serif;font-size:12px;font-weight:bold;line-height:1.2;display:inline-block;border-radius:3px&quot; href=&quot;https://unsplash.com/@emrekaratas?utm_medium=referral&amp;amp;utm_campaign=photographer-credit&amp;amp;utm_content=creditBadge&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; title=&quot;Photo by Emre Karataş&quot;&gt;&lt;span style=&quot;display:inline-block;padding:2px 3px&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; style=&quot;height:12px;width:auto;position:relative;vertical-align:middle;top:-2px;fill:white&quot; viewBox=&quot;0 0 32 32&quot;&gt;&lt;title&gt;unsplash-logo&lt;/title&gt;&lt;path d=&quot;M10 9V0h12v9H10zm12 5h10v18H0V14h10v9h12v-9z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;&lt;span style=&quot;display:inline-block;padding:2px 3px&quot;&gt;Photo by Emre Karataş&lt;/span&gt;&lt;/a&gt;&lt;/h5&gt;

&lt;h2 id=&quot;starting-off&quot;&gt;Starting off&lt;/h2&gt;
&lt;p&gt;For starters, I want to have a small web application that would be similar to something I could already have running in a regular Azure App Service with a CI/CD pipeline in Azure DevOps. To create something I used these steps:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Create a new repository on GitHub &lt;a href=&quot;https://github.com/rajbos/dotnetcore-webapp&quot;&gt;link&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Clone it to a local folder on my laptop&lt;/li&gt;
  &lt;li&gt;Move into the new directory&lt;/li&gt;
  &lt;li&gt;Run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotnet new webapp&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Update the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.gitignore&lt;/code&gt; file&lt;/li&gt;
  &lt;li&gt;Commit the first version of the code&lt;/li&gt;
  &lt;li&gt;Push into the remote on GitHub&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For testing how the new application worked and if it could be run I used these commands:&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;dotnet&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;restore&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dotnet&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dotnet&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;github-actions&quot;&gt;GitHub Actions&lt;/h2&gt;
&lt;p&gt;To get started with &lt;a href=&quot;https://github.com/features/actions&quot;&gt;GitHub Actions&lt;/a&gt; (do scroll on that site, or you will miss a lot of good stuff!) I had to join the Beta program and from then on I had an ‘Actions’ tab available on all my repositories. Note: the beta for GitHub Actions ends somewhere in November 2019.&lt;/p&gt;

&lt;p&gt;Clicking on ‘Actions’ on a repository on GitHub analyzes your repo and proposes a starter yaml file to use, based on the code they found in the repo. On mine, they correctly found some .NET code:
&lt;img src=&quot;/images/2019/20191026/01_After_clicking_actions_on_a_repo.png&quot; alt=&quot;After click &apos;Actions&apos; on a repository on GitHub&quot; /&gt;&lt;/p&gt;

&lt;p&gt;There are a lot of actions available already, as are other setup possibilities to get you started, regardless of your stack:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20191026/02_Other_setup_possibilities.png&quot; alt=&quot;Other setup possibilities&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The yaml file that you create is a set of actions to run after each other (some parallelization is possible) and is called a &lt;strong&gt;workflow&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;net-core-set-up&quot;&gt;.NET Core set up&lt;/h2&gt;
&lt;p&gt;I picked the suggested setup since it matched the .NET Core project I wanted to deploy into Azure. You can see that there are steps to set up the Action runner with the tooling it needs to build a .NET Core project and then we start a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build&lt;/code&gt; with the dotnet commands.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20191026/03_yml_file.png&quot; alt=&quot;yaml file&quot; /&gt;&lt;/p&gt;

&lt;p&gt;After saving the yaml file it will be committed to your repository and this will kick off a run, since it the trigger for this ‘workflow’ is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;on: push&lt;/code&gt;. You can see the commit ID that is associated with the push, as well as the branch and the account that pushed the changes:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20191026/04_After_saving_the_yml.png&quot; alt=&quot;After saving the yaml file&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;first-run&quot;&gt;First run&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2019/20191026/05_First_run_result.png&quot; alt=&quot;After saving the yaml file&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The first run I start reported an error. See the screenshot below for the message detail. This is already a good start: it clearly states what is wrong, as well as where! I am running the latest [].NET Core 3.0](https://dotnet.microsoft.com/) bits on my machine and the default yaml template uses the 2.2 version! I naively changed that to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3.0&lt;/code&gt;, but that run failed as well: you need to use the full SDK version, so &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3.0.100&lt;/code&gt; did the trick.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20191026/05a_Error_Message.png&quot; alt=&quot;Error message detail&quot; /&gt;&lt;/p&gt;

&lt;p&gt;First working run result: nice and fast!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20191026/06_Complete_Run.png&quot; alt=&quot;Complete run&quot; /&gt;
Also some things that stand out: the GitHub site doesn’t automatically show new runs on pushes and you cannot get back to the list of runs to switch to a previous run for example: you can click the ‘Actions’ tab again to start on the top-level of the ‘Workflows’ overview: it is possible to have multiple workflows in the same repository (handy!). The rest of the UI does look great though!&lt;/p&gt;

&lt;h2 id=&quot;publishing-to-azure-app-service&quot;&gt;Publishing to Azure App Service&lt;/h2&gt;
&lt;p&gt;To be able to publish the web app to an Azure App Service I create a new App Service on an existing App Service Plan. I found some &lt;a href=&quot;https://azure.github.io/AppService/2019/08/10/Github-actions-for-webapps.html#add-the-app-service-action&quot;&gt;documentation&lt;/a&gt; on using GitHub actions from the App Service team, so I tried that out first. The examples are specific to running a docker container on the App Service, or a Java, Javascript or Python application.  Surely, this will work for .NET Core as well?&lt;/p&gt;

&lt;p&gt;To prepare your repo, download the publish profile for the App Service and store it in a &lt;a href=&quot;https://help.github.com/en/github/automating-your-workflow-with-github-actions/virtual-environments-for-github-actions#creating-and-using-secrets-encrypted-variables&quot;&gt;GitHub Secret&lt;/a&gt;. Here is some strange UI as well: I used the secret name from the documentation I was following and it contained a dash between the words. This is not a valid secret name, so GitHub will show you an error. Adding the correct name (I changed it to an underscore) shows a green notification. This UI can be confusing: which message belongs to which values? Maybe the could add the secret name that you where trying or at least a timestamp to it?&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20191026/07_Secret_storing_error_on_the_name_with_a_dash.png&quot; alt=&quot;Secret storing error double message&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Just adding this step in the yaml file ran the workflow again and indicated a warning: this space is moving so fast as it is just a couple of months old! The warning states that the action I am using is already deprecated: I need to use a different one!&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;##[warning]This action is moved to azure/webapps-deploy repository, update your workflows to use those actions instead.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;To bad this warning doesn’t include a link to the correct repository. Luckily the name of the action is also its path after GitHub.com: &lt;a href=&quot;https://github.com/azure/webapps-deploy&quot;&gt;https://github.com/azure/webapps-deploy&lt;/a&gt;. Scrolling through the repository shows even more example yaml files to use! For .NET Core the example can be found &lt;a href=&quot;https://wikipedia.org/wiki/Linkrot&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h5 id=&quot;do-note-that-this-also-adds-a-publish-step-i-forgot-that-one-initially&quot;&gt;Do note that this also adds a publish step: I forgot that one initially!&lt;/h5&gt;

&lt;p&gt;By now my mail application has caught up as well: on each failed run, GitHub sends you an email message to make sure you know something is not right 😄.&lt;/p&gt;
&lt;h5 id=&quot;do-note-you-can-change-that-behavior-on-your-profiles-settings-but-not-on-a-per-project-basis&quot;&gt;Do note you can change that behavior on your profiles &lt;a href=&quot;https://github.com/settings/notifications&quot;&gt;settings&lt;/a&gt;, but not on a per project basis.&lt;/h5&gt;
&lt;p&gt;&lt;img src=&quot;/images/2019/20191026/08_Failed_run_email_message.png&quot; alt=&quot;Failed run email message&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;complete-workflow&quot;&gt;Complete workflow&lt;/h1&gt;
&lt;p&gt;By now I have the current workflow that is running as I want it to:&lt;/p&gt;
&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;.NET Core&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v1&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Setup .NET Core&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/setup-dotnet@v1&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;dotnet-version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;3.0.100&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# dotnet build and publish&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Build with dotnet&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;dotnet build --configuration Release&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;dotnet publish&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;dotnet publish -c Release -o dotnetcorewebapp&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Run&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Azure&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;webapp&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;deploy&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;using&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;publish&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;profile&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;credentials&apos;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;azure/webapps-deploy@v1&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;app-name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;dotnetcorewebapp19&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Replace with your app name&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;publish-profile&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Define secret variable in repository settings as per action documentation&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;package&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;./dotnetcorewebapp&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And with that setup I have my first successful run!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20191026/09_Succesful_run.png&quot; alt=&quot;Successful run&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You can now browse to the website and see if it is running:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20191026/10_Website_running.png&quot; alt=&quot;Website is running&quot; /&gt;&lt;/p&gt;

&lt;p&gt;To be really sure, the files now exist in the Kudu editor:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20191026/11_Kudu_files_exists.png&quot; alt=&quot;File exist in Kudu editor&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;summary&quot;&gt;Summary&lt;/h1&gt;
&lt;p&gt;There you go, a complete example of setting up your first GitHub Actions Workflow for pushing a .NET Core application into an Azure App Service. The set of actions I used also has options for deploying to a deployment slot as well, witch makes it complete! If this is the first time you use yaml, this can look a bit daunting, but after reading what is happening, I figured everything out as well!&lt;/p&gt;

&lt;p&gt;Next up is seeing if we can parameterize this yaml file and deploy to different web applications (for example &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;staging&lt;/code&gt;) with the same template. That’s something for another time!&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Deploy Windows Service using Azure DevOps</title>
			<link href="https://devopsjournal.io/blog/2019/10/23/Azure-DevOps-Windows-Service"/>
			<updated>2019-10-23T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2019/10/23/Azure-DevOps-Windows-Service</id>
			<content type="html">&lt;p&gt;Ever needed to deploy a Windows Service onto a machine with Azure DevOps? It turns out this is really easy (some caveat’s apply 😄, see section at the bottom)!&lt;/p&gt;

&lt;p&gt;There is an &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=automagically.ManageRemoteWindowsService&quot;&gt;extension&lt;/a&gt; on the &lt;a href=&quot;https://marketplace.visualstudio.com&quot;&gt;Azure DevOps Marketplace&lt;/a&gt; that is a wrapper around the &lt;a href=&quot;https://support.microsoft.com/en-us/help/251192/how-to-create-a-windows-service-by-using-sc-exe&quot;&gt;SC tool&lt;/a&gt; from Windows:
&lt;img src=&quot;/images/2019/20191023/20191023_02_Extension.png&quot; alt=&quot;Azure DevOps screenshot&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Add the extension and perform the tasks that you need:
&lt;img src=&quot;/images/2019/20191023/20191023_01_Tasks.png&quot; alt=&quot;Azure DevOps screenshot&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In this case, I use these tasks:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;delete the existing service (will fail on the first try when the service is not available!)&lt;/li&gt;
  &lt;li&gt;copy the files to the destination path&lt;/li&gt;
  &lt;li&gt;set up the parameters for that server&lt;/li&gt;
  &lt;li&gt;install the service on the remote server&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;caveats&quot;&gt;Caveats&lt;/h2&gt;
&lt;p&gt;There are some small caveats to get this to work:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/windows/win32/winrm/portal?WT.mc_id=AZ-MVP-5003719&quot;&gt;WinRM&lt;/a&gt; needs to be enabled on the target server.&lt;/li&gt;
  &lt;li&gt;This extension cannot use the standard service user accounts to run the service as. This means you are tied to local user accounts on the target server and cannot use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Local System&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Network Service&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Only works for X64 server and installed agent.&lt;/li&gt;
&lt;/ol&gt;
</content>
		</entry>
	
		<entry>
			<title>Parallelizing a long Stryker Run in Azure DevOps</title>
			<link href="https://devopsjournal.io/blog/2019/10/11/Parallelizing-a-long-Stryker-Run-in-Azure-DevOps"/>
			<updated>2019-10-11T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2019/10/11/Parallelizing-a-long-Stryker-Run-in-Azure-DevOps</id>
			<content type="html">&lt;p&gt;I’ve been working on a &lt;a href=&quot;https://stryker-mutator.io/stryker-net/&quot;&gt;Stryker&lt;/a&gt; run for a larger .NET solution (115 projects and counting) and wanted to document on the final setup in Azure DevOps.&lt;/p&gt;

&lt;p&gt;You can find more information on what Stryker is and how this can be used on a .NET project with an example on this previous &lt;a href=&quot;/blog/2019/08/29/Use-Stryker-Azure-DevOps&quot;&gt;blog post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this post you can find how I got to this point: &lt;a href=&quot;/blog/2019/10/04/Runnning-Stryker-in-a-large-solution&quot;&gt;link&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20191011/dallas-reedy-NEJFAS1Okho-unsplash.jpg&quot; alt=&quot;Bird soaring&quot; /&gt;&lt;/p&gt;
&lt;h5 id=&quot;unsplash-logophoto-by-dallas-reedy&quot;&gt;&lt;a style=&quot;background-color:black;color:white;text-decoration:none;padding:4px 6px;font-family:-apple-system, BlinkMacSystemFont, &amp;quot;San Francisco&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Ubuntu, Roboto, Noto, &amp;quot;Segoe UI&amp;quot;, Arial, sans-serif;font-size:12px;font-weight:bold;line-height:1.2;display:inline-block;border-radius:3px&quot; href=&quot;https://unsplash.com/@dallasreedy?utm_medium=referral&amp;amp;utm_campaign=photographer-credit&amp;amp;utm_content=creditBadge&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; title=&quot;Photo by Dallas Reedy&quot;&gt;&lt;span style=&quot;display:inline-block;padding:2px 3px&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; style=&quot;height:12px;width:auto;position:relative;vertical-align:middle;top:-2px;fill:white&quot; viewBox=&quot;0 0 32 32&quot;&gt;&lt;title&gt;unsplash-logo&lt;/title&gt;&lt;path d=&quot;M10 9V0h12v9H10zm12 5h10v18H0V14h10v9h12v-9z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;&lt;span style=&quot;display:inline-block;padding:2px 3px&quot;&gt;Photo by Dallas Reedy&lt;/span&gt;&lt;/a&gt;&lt;/h5&gt;

&lt;h1 id=&quot;final-setup-in-azure-devops&quot;&gt;Final Setup in Azure DevOps&lt;/h1&gt;
&lt;p&gt;You can find the repository with all the scripts on &lt;a href=&quot;https://github.com/rajbos/Stryker.MultipleProjectRunner&quot;&gt;GitHub&lt;/a&gt;. I call these scripts in the Azure DevOps pipelines.&lt;/p&gt;

&lt;h2 id=&quot;high-level-overview&quot;&gt;High level overview&lt;/h2&gt;
&lt;p&gt;See the screenshot below for the final setup. Jobs 1 &amp;amp; 2 will run in parallel since they aren’t linked to any other job as a &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/pipelines/process/phases?view=azure-devops&amp;amp;tabs=classic#dependencies&amp;amp;WT.mc_id=DOP-MVP-5003719&quot;&gt;dependency&lt;/a&gt;. This enables us to run multiple Stryker jobs (each with multiple projects!) at the same time. If there are enough eligible agents in the build pool, we can fan out this rather extensive task. Depending on the number of unit tests and the code that is being tested, mutation testing can easily take a while.&lt;/p&gt;

&lt;p&gt;Task 3 &lt;strong&gt;does&lt;/strong&gt; have a dependency on the other tasks, so it will run when task 1 and 2 are completed successfully.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20191011/20191011_01_Overview.png&quot; alt=&quot;Build overview&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;running-stryker-on-a-set-of-projects&quot;&gt;Running Stryker on a set of projects&lt;/h2&gt;
&lt;p&gt;Job 1 and 2 have the same taskgroup that will run.&lt;/p&gt;
&lt;h5 id=&quot;note-read-my-previous-post-on-the-set-up-i-use-to-run-stryker-on-a-set-of-projects-this-job-runs-stryker-on-all-projects-in-a-given-folder-by-creating-a-specific-configuration-file-for-that-part-of-the-solution-the-next-task-then-uploads-all-the-json-files-back-into-azure-devops-so-they-will-be-available-for-downloading-in-a-later-step-this-is-needed-because-each-agent-job-willcan-run-on-a-different-agent-and-therefore-the-json-files-will-be-generated-in-a-different-folder&quot;&gt;Note: read my previous &lt;a href=&quot;/blog/2019/10/04/Runnning-Stryker-in-a-large-solution&quot;&gt;post&lt;/a&gt; on the set up I use to run Stryker on a set of projects. This job runs Stryker on all projects in a given folder by creating a specific configuration file for that part of the solution. The next task then uploads all the json files back into Azure DevOps so they will be available for downloading in a later step. This is needed because each agent job will/can run on a different agent and therefore the json files will be generated in a different folder.&lt;/h5&gt;
&lt;p&gt;&lt;img src=&quot;/images/2019/20191011/20191011_02_RunStrykerfromSettingsfile.png&quot; alt=&quot;Build overview&quot; /&gt;
Because each job can run on a different agent, we need to build the solution on each agent to make sure that Stryker can run. I’ve asked the Stryker team why they need this method and to see if we can do that once instead of in each job &lt;a href=&quot;https://github.com/stryker-mutator/stryker-net/issues/762&quot;&gt;here&lt;/a&gt;. Still, the tradeoff with the ability to run on different agents is worth it. If I can change this setup, I will update this post.&lt;/p&gt;

&lt;p&gt;After building the solution I need to make sure that the agent has the Stryker tooling installed. I don’t like to do that on an agent by hand or by baking tools like this into an agent image. I’d rather have the tool installation available in the build itself. That enables us to add new agents to the pool when needed, without us having to do something to make sure all our tooling works. Checking for a .NET Core tools on a server can be done with the code in this &lt;a href=&quot;https://gist.github.com/rajbos/b148e9833a5d08165188dbe00cc32301&quot;&gt;GitHub Gist&lt;/a&gt;.
Using PowerShell I then download the files from my &lt;a href=&quot;https://github.com/rajbos/Stryker.MultipleProjectRunner&quot;&gt;GitHub repository&lt;/a&gt; that has all the code we need to &lt;a href=&quot;/blog/2019/10/04/Runnning-Stryker-in-a-large-solution&quot;&gt;run Stryker on multiple projects&lt;/a&gt; and join their results. Those results are then copied into the Artifact directory. That way I can pick them up and upload them to the Artifacts linked to this build.&lt;/p&gt;

&lt;p&gt;I update the settingsfile here to correct the hard-coded paths in the configuration.json so the tools can find them in the agents source code directory.&lt;/p&gt;

&lt;h2 id=&quot;joining-the-stryker-results&quot;&gt;Joining the Stryker results&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2019/20191011/20191011_03_CreateStrykerReport.png&quot; alt=&quot;Build overview&quot; /&gt;
This job will first download all the artifacts from the other jobs so we have them available. By using the same code in my &lt;a href=&quot;https://github.com/rajbos/Stryker.MultipleProjectRunner&quot;&gt;GitHub repository&lt;/a&gt;, I can now join those json files and create a new report from it.&lt;/p&gt;

&lt;p&gt;As a final step I upload the generated HTML file (self contained btw, very nice) to the artifacts for the build so they can be downloaded and analyzed.&lt;/p&gt;

&lt;h3 id=&quot;todo&quot;&gt;Todo&lt;/h3&gt;
&lt;p&gt;What I haven’t done yet, is failing the build on a low mutation score. I’m not sure what is helpful here: I could store the results from each run and use the lowest score to verify it against a threshold, or I could try to calculate an overall score from the results files. Unfortunately that information is not stored in the results json, so that is &lt;a href=&quot;https://github.com/stryker-mutator/stryker-net/issues/763&quot;&gt;currently not possible&lt;/a&gt;. Still, having a (regular) checkup on you unit tests is already a nice improvement to have!&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>SonarQube analysis on a Java project - fixing error &apos;Project was never analyzed&apos;</title>
			<link href="https://devopsjournal.io/blog/2019/10/07/SonarQube-Azure-DevOps-Java-Error"/>
			<updated>2019-10-07T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2019/10/07/SonarQube-Azure-DevOps-Java-Error</id>
			<content type="html">&lt;p&gt;Today I was configuring a SonarQube Analysis in &lt;a href=&quot;https://dev.azure.com&quot;&gt;Azure DevOps&lt;/a&gt; on a Java project. Following the &lt;a href=&quot;https://sonarcloud.io/documentation/analysis/scan/sonarscanner-for-azure-devops/&quot;&gt;documentation&lt;/a&gt; I still got this error:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[ERROR] Failed to execute goal org.sonarsource.scanner.maven:sonar-maven-plugin:3.7.0.1746:sonar (default-cli) on project &apos;prefix-project&apos; Project was never analyzed. A regular analysis is required before a branch analysis -&amp;gt; [Help 1]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Given the error message says it is an error for the project &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prefix-project&lt;/code&gt; I guessed that the plugin wanted to link everything to a project with that key in SonarQube. Since I was creating this in a different Project Team, there might be an issue with the new service connection (those are tied to the team project). So to test, I created a project in SonarQube with the same key.&lt;/p&gt;

&lt;p&gt;Running another build resulted in the same error.
Running a build on the master branch, just to make sure it wasn’t related to a different branch and I need to run it first on on the master branch: same error.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20191007/20191007_BuildError.png&quot; alt=&quot;Build Error&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In the pom file I noticed that there was an extra &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;groupId&lt;/code&gt; added:&lt;/p&gt;
&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.organizationname&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
 &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;prefix-project&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;fixed-project-key&quot;&gt;Fixed project key&lt;/h2&gt;
&lt;p&gt;And running the build in debug mode showed that it was using that combination as a project key for the SonarQube project. So, changed the project key in SonarQube to use that:
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;com.organizationname:prefix-project&lt;/code&gt;&lt;/p&gt;

&lt;h2 id=&quot;tasks&quot;&gt;Tasks&lt;/h2&gt;
&lt;p&gt;Using that project key with the default tasks now works.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Prepare step before running with Maven:
&lt;img src=&quot;/images/2019/20191007/20191007_BuildDefPrepare.png&quot; alt=&quot;Prepare before maven&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Update Maven build tasks to push to SonarQube:
&lt;img src=&quot;/images/2019/20191007/20191007_BuildDefMaven.png&quot; alt=&quot;Prepare before maven&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;
</content>
		</entry>
	
		<entry>
			<title>Running Stryker on a large .NET / core solution</title>
			<link href="https://devopsjournal.io/blog/2019/10/04/Runnning-Stryker-in-a-large-solution"/>
			<updated>2019-10-04T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2019/10/04/Runnning-Stryker-in-a-large-solution</id>
			<content type="html">&lt;h2 id=&quot;tldr&quot;&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://stryker-mutator.io/stryker-net/&quot;&gt;Stryker&lt;/a&gt; cannot run for an entire solution with multiple test projects (YET), so we need to help it a little and run each project by itself and then join the results. Finding a way to do so started by checking in with the Stryker team on &lt;a href=&quot;https://github.com/stryker-mutator/stryker-net/issues/740&quot;&gt;GitHub&lt;/a&gt;. I ❤️ OSS!
They are working at making this part easier, so I checked out their code to see if I could help with that. That proved to be rather hard! There is a lot going on under the covers. Still want to help, but first I tried to get a quick fix that I can bring back to the team that wants to run Stryker for their entire solution file, with 112 projects in it (and only growing 😱). Yes, they should see what else needs fixing 😁.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20191004/20191004_Stryker_Logo.png&quot; alt=&quot;Stryker logo&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;using-stryker-in-net-code&quot;&gt;Using Stryker in .NET code&lt;/h2&gt;
&lt;p&gt;I previously posted about using Stryker for .NET in your Azure Pipeline (find it &lt;a href=&quot;/blog/2019/09/04/Use-Stryker-Azure-DevOps&quot;&gt;here&lt;/a&gt;). After running it for one project, I now want to run it for a lot of projects, in the same solution. Currently, this is not available in the Stryker tooling. After &lt;a href=&quot;https://github.com/stryker-mutator/stryker-net/issues/740&quot;&gt;reaching&lt;/a&gt; out to Stryker team, I had confirmation that the way I wanted to do things, seemed like the correct way to go:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Run Stryker on each project&lt;/li&gt;
  &lt;li&gt;Gather the output files from each run&lt;/li&gt;
  &lt;li&gt;Join all files together&lt;/li&gt;
  &lt;li&gt;Run the full report with the new file&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I created all code to do this in a PowerShell file and have shared that on &lt;a href=&quot;https://github.com/rajbos/Stryker.MultipleProjectRunner&quot;&gt;GitHub&lt;/a&gt;. All information on how to do things is in de readme of that repository.&lt;/p&gt;

&lt;h3 id=&quot;files&quot;&gt;Files&lt;/h3&gt;
&lt;p&gt;I’ve used these file to set things up:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Run Stryker.ps1&lt;/li&gt;
  &lt;li&gt;Stryker.data.json&lt;/li&gt;
  &lt;li&gt;StrykerReportEmpty.html&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The PowerShell file is the entry point of course. It will use the json file for configuration so we can push in multiple project files to mutate. All the generated json will be copied over to a new output folder and will then be joined together,
From that, I can paste it into the HTML file and store that as a new result file. That gives us the final html file that is self contained.&lt;/p&gt;

&lt;h2 id=&quot;next-step&quot;&gt;Next step&lt;/h2&gt;
&lt;p&gt;The next step is to update the code in the repo so I can do things like running multiple projects in multiple runs through this script (for example in Azure DevOps, where this can be parallelized), and join the resulting json files back together with an extra call at the end. That means I need to make the current code a little bit more modularized 😄.&lt;/p&gt;

&lt;p&gt;For now, this is already a working solution, so: Happy mutating!&lt;/p&gt;

&lt;h2 id=&quot;update&quot;&gt;Update:&lt;/h2&gt;
&lt;p&gt;For the final setup with all steps running in parallel in Azure DevOps you can find the end result &lt;a href=&quot;/blog/2019/10/11/Parallelizing-a-long-Stryker-Run-in-Azure-DevOps&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Installing .NET Core tools: Preventing errors in your Azure Pipelines</title>
			<link href="https://devopsjournal.io/blog/2019/09/25/Installing-dotnet-core-tools-preventing-errors-in-your-azure-pipelines"/>
			<updated>2019-09-25T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2019/09/25/Installing-dotnet-core-tools-preventing-errors-in-your-azure-pipelines</id>
			<content type="html">&lt;p&gt;I ran into a weird thing in .NET Core Global tools: If you try to install the tools while they are already installed on that system, .NET Core will throw an error and exit with a non-zero exit code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is not helpful in a Continuous Integration (CI) scenario!&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;expected-installation&quot;&gt;Expected installation&lt;/h2&gt;
&lt;p&gt;Normally you expect that you run an install command like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-PowerShell&quot;&gt;dotnet tool install dotnet-stryker -g
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And the tool would install. This is correct if you run it once.&lt;/p&gt;

&lt;p&gt;In Azure DevOps you would call the command from a .NET Core task, as I previously described &lt;a href=&quot;/blog/2019/09/03/Running-dotnet-tools-in-azure-devops&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190925/2019-09-25_InstallCommand.png&quot; alt=&quot;Install command in Azure Pipelines&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If you run the pipeline again, or the command locally, you get an error indicating that the tool is already installed!
&lt;img src=&quot;/images/2019/20190925/2019-09-25_InstallFails.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This behavior seems rather odd to me: if the tool is already installed, then great. Perform a no-op (no operation) and go ahead with the next command.&lt;/p&gt;

&lt;p&gt;In &lt;a href=&quot;https://github.com/dotnet/cli/issues/9482&quot;&gt;this issue&lt;/a&gt; on GitHub this behavior is described. The team has made a choice about doing it this way, even though they are not sure that this was the &lt;a href=&quot;https://github.com/dotnet/cli/issues/11494#issuecomment-499716465&quot;&gt;right choice&lt;/a&gt;. The current debate seems to be if the decision can be reverted (that would mean a breaking change from the old behavior) or adding a new parameter that would enforce the expected behavior.&lt;/p&gt;

&lt;h1 id=&quot;workaround&quot;&gt;Workaround&lt;/h1&gt;
&lt;p&gt;The work around for this issue is almost as weird as the issue it self. You run the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;update&lt;/code&gt; command for the tool:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-PowerShell&quot;&gt;dotnet tool update dotnet-stryker -g
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Outcome:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;if the tool is not installed, it will install the latest version!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Seems strange behavior to me, but ok… At least it will return an exit code of 0 and we can move along.&lt;/p&gt;

&lt;p&gt;If you run the command again, the tool will try to upgrade to the latest version, which is what I would expect.
The logs do seem to indicate an interesting story… It seems to  reinstall that same version (given there is no newer version available)…!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190925/2019-09-25_UpdateCommandResult.png&quot; alt=&quot;Result of dotnet update&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Strange way of implementing this functionality, at least there is a work around to prevent the CI build from failing.&lt;/p&gt;

&lt;h2 id=&quot;update-for-multiple-parallel-executions&quot;&gt;Update for multiple parallel executions&lt;/h2&gt;
&lt;p&gt;I needed to run a dotnet tool parallel on multiple machines in the same pipeline and then this method does not work. Seemed like a conflict when running the update command during the same time window.&lt;/p&gt;

&lt;p&gt;To circumvent this, I had to create a small PowerShell script to check the output of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotnet tool list -g&lt;/code&gt; command and first manually check if the tool was installed. You can find the code for it in this &lt;a href=&quot;https://gist.github.com/rajbos/b148e9833a5d08165188dbe00cc32301&quot;&gt;Gist&lt;/a&gt;.&lt;/p&gt;
&lt;h5 id=&quot;note-not-sure-why-the-last-exitcode-is-not-0-when-running-the-regular-net-core-command-in-an-azure-devops-pipeline-so-i-had-to-enforce-a-normal-exit-code&quot;&gt;Note: Not sure why the last exitcode is not 0 when running the regular .NET Core command in an Azure DevOps pipeline, so I had to enforce a normal exit code.&lt;/h5&gt;
</content>
		</entry>
	
		<entry>
			<title>Use Stryker for .NET code in Azure DevOps</title>
			<link href="https://devopsjournal.io/blog/2019/09/04/Use-Stryker-Azure-DevOps"/>
			<updated>2019-09-04T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2019/09/04/Use-Stryker-Azure-DevOps</id>
			<content type="html">&lt;p&gt;Recently I was at a customer where they where testing running test mutation with  &lt;a href=&quot;https://stryker-mutator.io/stryker-net/&quot;&gt;Stryker&lt;/a&gt;. Mutation testing is a concept where you change the code in your System Under Test (SUT) to see if your unit test would actually fail. If they don’t, your unit tests aren’t specific enough for the SUT and should be re-evaluated. Since Stryker changes your code, they call it &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mutations&lt;/code&gt; and they check if they &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;survive&lt;/code&gt; with the unit tests or not. Nice play on words there 😄.&lt;/p&gt;

&lt;p&gt;Of course this triggered me to see how this works with .NET code and if we can integrate this in Azure DevOps!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190829/suzanne-d-williams-VMKBFR6r_jg-unsplash.jpg&quot; alt=&quot;Hero image&quot; /&gt;&lt;/p&gt;
&lt;h5 id=&quot;unsplash-logophoto-by-suzanne-d-williams&quot;&gt;&lt;a style=&quot;background-color:black;color:white;text-decoration:none;padding:4px 6px;font-family:-apple-system, BlinkMacSystemFont, &amp;quot;San Francisco&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Ubuntu, Roboto, Noto, &amp;quot;Segoe UI&amp;quot;, Arial, sans-serif;font-size:12px;font-weight:bold;line-height:1.2;display:inline-block;border-radius:3px&quot; href=&quot;https://unsplash.com/@scw1217?utm_medium=referral&amp;amp;utm_campaign=photographer-credit&amp;amp;utm_content=creditBadge&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; title=&quot;Photo by Suzanne D. Williams&quot;&gt;&lt;span style=&quot;display:inline-block;padding:2px 3px&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; style=&quot;height:12px;width:auto;position:relative;vertical-align:middle;top:-2px;fill:white&quot; viewBox=&quot;0 0 32 32&quot;&gt;&lt;title&gt;unsplash-logo&lt;/title&gt;&lt;path d=&quot;M10 9V0h12v9H10zm12 5h10v18H0V14h10v9h12v-9z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;&lt;span style=&quot;display:inline-block;padding:2px 3px&quot;&gt;Photo by Suzanne D. Williams&lt;/span&gt;&lt;/a&gt;&lt;/h5&gt;

&lt;h2 id=&quot;setting-up-an-example&quot;&gt;Setting up an example&lt;/h2&gt;
&lt;p&gt;To have a SUT and a set of unit tests for this, I have set up a small C# library with .NET Core and some unit tests to run. I created a solution that only contains what we need:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190829/2019-08-29_SolutionExplorer.png&quot; alt=&quot;Example of Visual Studio Solution explorer with the two projects&quot; /&gt;&lt;/p&gt;

&lt;p&gt;To have something to unit test I added a simple class that would be instantiated with a string and we load that as a boolean value into a property of the class.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190829/2019-08-29_StrykerDemo.Class1.png&quot; alt=&quot;Layout of class1&quot; /&gt;&lt;/p&gt;

&lt;p&gt;To check if my class setup works, I created two unit tests as an example for both the parameter values ‘True’ and ‘False’. You can probably spot the first issue here: I am using different casings in my unit tests, but they still pass the test runs.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190829/2019-08-29_StrykerDemo.UnitTests.png&quot; alt=&quot;Example of unit tests for both value &apos;True&apos; and &apos;False&apos;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I’ve already set up an Azure DevOps build to trigger on any pushes to the repo, with the default .NET Core template to restore the NuGet packages, run a Build and then run the Unit Tests:
&lt;img src=&quot;/images/2019/20190829/2019-09-04_StrykerAzureDevOps.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The Azure DevOps build is green with the current set of tests:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190829/2019-08-29AzureDevOpsBuild.png&quot; alt=&quot;Azure DevOps build pipeline is green with these tests&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Using &lt;a href=&quot;https://stryker-mutator.io/stryker-net/quickstart&quot;&gt;Stryker for .NET&lt;/a&gt; can by done on the CLI by installing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotnet tool&lt;/code&gt; with this command:&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;dotnet&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;tool&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;install&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dotnet-stryker&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;I’ve ran into some configuration issues and an older version of .NET Core that I have documented &lt;a href=&quot;/blog/2019/09/03/fixing-error-.NET-core-dotnet-new-tool-manifest&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When you run the tool from the CLI, you can see the results immediately and also see the changes Stryker has made to your code.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190829/2019-08-29WindowsTerminalInstallStryker.png&quot; alt=&quot;Commands to install and run Stryker&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In the screenshot above you can see that by default, running Stryker in your solution folder will not work. Stryker wants to be run from the folder containing the UnitTests project and will pick it up automatically. It will also find the solution and the project containing the code it needs to mutate. If you need, you can help Stryker find all this by adding some parameters that have been documented &lt;a href=&quot;https://stryker-mutator.io/docs/stryker-net/mutations#unary-operators-unary&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;running-stryker-in-the-cli&quot;&gt;Running Stryker in the CLI&lt;/h2&gt;
&lt;p&gt;Running Stryker from the unit test project directory will start mutating your code and running the unit tests on it again. It will try out all the mutations it can find and then track if it survived all the unit tests (meaning that there was at least one unit test that failed when running against the mutation). The results are visible inline.&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;dotnet&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stryker&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--reporters&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[&apos;cleartext&apos;, &apos;html&apos;]&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190829/2019-08-29_TerminalStrykerRun.png&quot; alt=&quot;Executing Stryker&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;mutations&quot;&gt;Mutations&lt;/h2&gt;
&lt;p&gt;As you can see in the screenshot above, Stryker searches the original code for boolean expressions, strings and other things it can ‘&lt;a href=&quot;https://stryker-mutator.io/docs/stryker-net/mutations&quot;&gt;mutate&lt;/a&gt;’.&lt;/p&gt;

&lt;p&gt;The first mutation in this run was changing the line &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if (isOpen == &quot;true&quot;)&lt;/code&gt; into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if (isOpen == &quot;&quot;)&lt;/code&gt; (a string mutation). This mutation is caught by the first unit test and therefore marked as ‘killed’.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190829/2019-09-04StrykerMutation.png&quot; alt=&quot;Mutation example&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;stryker-report&quot;&gt;Stryker report&lt;/h2&gt;
&lt;p&gt;Adding a html report parameter to the Stryker command will write a html file to your disk that can be used for finding the mutations that either survived and where killed.&lt;/p&gt;

&lt;h3 id=&quot;summary-view&quot;&gt;Summary view&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/images/2019/20190829/2019-08-29StrykerReport.png&quot; alt=&quot;Stryker report for the tests&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;details-view&quot;&gt;Details view&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/images/2019/20190829/2019-08-29StrykerReportDetails.png&quot; alt=&quot;Stryker detailed report for the tests&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;adding-stryker-to-your-azure-devops-pipeline&quot;&gt;Adding Stryker to your Azure DevOps pipeline&lt;/h2&gt;
&lt;p&gt;Now you are ready to include a Stryker run into your Azure DevOps build pipeline. To do so, you can include calls to the dotnet tool commands using the normal .NET Core task. If you need help figuring out how to set up the custom commands, read my blogpost about &lt;a href=&quot;/blog/2019/09/03/Running-dotnet-tools-in-azure-devops&quot;&gt;Running dotnet tools in Azure DevOps&lt;/a&gt;.
Do note the specific arguments I pass into the Stryker command here: my mutation tests where scoring on 54%, so I needed custom thresholds to actually fail the build.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190829/2019-09-04_StrykerAzureDevOpsConfig.png&quot; alt=&quot;Azure DevOps Steps to run Stryker in the build pipeline&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;failed-build-result&quot;&gt;Failed build result&lt;/h2&gt;
&lt;p&gt;Running Stryker on my current set of tests will actually fail the build because of the custom threshold. This way you can validate your unit tests and actually check to see if there are any outliers that you missed while creating the tests for your code.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190829/2019-09-04_AzureDevOpsFailedBuild.png&quot; alt=&quot;Fail the Azure DevOps build&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; mutating your code and running the unit tests again means that your tests will run multiple times. This &lt;em&gt;can&lt;/em&gt; add up to quite some additional time that your build needs to run!&lt;/p&gt;

&lt;h2 id=&quot;next-step&quot;&gt;Next step&lt;/h2&gt;
&lt;p&gt;The next step is to include the html report in you build pipeline and upload it as an artifact. You can then download it if you need to check it.&lt;/p&gt;

&lt;p&gt;The Stryker team seems to be working on an extension for Azure DevOps to enable the build results to show an extra tab that would open that artefact file, but it seems that this is not yet ready. Keep up to date on this by watching this &lt;a href=&quot;https://github.com/stryker-mutator/azure-devops-mutationreport-publisher&quot;&gt;repository&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id=&quot;update&quot;&gt;Update&lt;/h1&gt;
&lt;p&gt;Read more information about the setup in Azure DevOps in my follow up post &lt;a href=&quot;/blog/2019/08/29/Use-Stryker-Azure-DevOps&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For the final setup with all steps running in parallel in Azure DevOps you can find the end result &lt;a href=&quot;/blog/2019/10/11/Parallelizing-a-long-Stryker-Run-in-Azure-DevOps&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Fixing error in .NET Core tool installation</title>
			<link href="https://devopsjournal.io/blog/2019/09/03/fixing-error-.NET-core-dotnet-new-tool-manifest"/>
			<updated>2019-09-03T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2019/09/03/fixing-error-.NET-core-dotnet-new-tool-manifest</id>
			<content type="html">&lt;p&gt;Last week I was testing some .NET tooling and wanted to install a tool locally instead of globally. To do so you run this command:&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;dotnet&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;tool&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;install&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dotnet-stryker&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;While running (either locally or in an &lt;a href=&quot;https://dev.azure.com&quot;&gt;Azure DevOps&lt;/a&gt; task) I got this error message:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cmd&quot;&gt;[command]&quot;C:\Program Files\dotnet\dotnet.exe&quot; tool install dotnet-stryker
Cannot find any manifests file. Searched:
C:\Apps\TFSAgent\_work\7\s\StrykerDemo.UnitTests\.config\dotnet-tools.json
C:\Apps\TFSAgent\_work\7\s\StrykerDemo.UnitTests\dotnet-tools.json
C:\Apps\TFSAgent\_work\7\s\.config\dotnet-tools.json
C:\Apps\TFSAgent\_work\7\s\dotnet-tools.json
C:\Apps\TFSAgent\_work\7\.config\dotnet-tools.json
C:\Apps\TFSAgent\_work\7\dotnet-tools.json
C:\Apps\TFSAgent\_work\.config\dotnet-tools.json
C:\Apps\TFSAgent\_work\dotnet-tools.json
C:\Apps\TFSAgent\.config\dotnet-tools.json
C:\Apps\TFSAgent\dotnet-tools.json
C:\Apps\.config\dotnet-tools.json
C:\Apps\dotnet-tools.json
C:\.config\dotnet-tools.json
C:\dotnet-tools.json
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190903/20190903-dotnet-core-logo.png&quot; alt=&quot;.NET Core logo&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Searching around on the internet for the file it is searched (throughout the whole folder tree), I found that you need to run this command to create a local manifest:&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;dotnet&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;tool-manifest&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Yet doing so resulted in the following error message and the default prompt to choose a template to run.&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;No&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;templates&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;matched&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;the&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Apparently the command to generate the manifest wasn’t available on my machine. Further searching lead to this &lt;a href=&quot;https://github.com/dotnet/cli/issues/10499&quot;&gt;GitHub issue&lt;/a&gt; that pointed out this was recently added in .NET Core 3.0, so it seemed that it could be coming from an old preview version?&lt;/p&gt;

&lt;p&gt;Checking the runtimes I had installed with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotnet --list-runtimes&lt;/code&gt;, pointed out that I was indeed running on on older version of the preview for .NET Core 3.0.&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;Microsoft.NETCore.App&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;2.2.4&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;C:\Program&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Files\dotnet\shared\Microsoft.NETCore.App&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Microsoft.NETCore.App&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;3.0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;0-preview-27113-06&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;C:\Program&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Files\dotnet\shared\Microsoft.NETCore.App&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Microsoft.NETCore.App&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;3.0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;0-preview-27114-01&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;C:\Program&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Files\dotnet\shared\Microsoft.NETCore.App&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Downloading and installing the &lt;a href=&quot;https://dotnet.microsoft.com/download/dotnet-core/3.0&quot;&gt;latest preview&lt;/a&gt; (3.0.0-preview8-28405-07 at the time of writing) fixed the issue and I could carry on with figuring out my other steps that I was actually working on 😄.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Running .NET Core tools in Azure DevOps</title>
			<link href="https://devopsjournal.io/blog/2019/09/03/Running-dotnet-tools-in-azure-devops"/>
			<updated>2019-09-03T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2019/09/03/Running-dotnet-tools-in-azure-devops</id>
			<content type="html">&lt;p&gt;I wanted to run .NET Core tools in Azure DevOps and ran into some installation issues. I tried installing the tool I needed globally, yet the agent could not find it.&lt;/p&gt;

&lt;h2 id=&quot;local-tools-to-the-rescue&quot;&gt;Local tools to the rescue&lt;/h2&gt;
&lt;p&gt;In the latest versions of .NET Core 3.0 (currently still in preview), you can install tools &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;locally&lt;/code&gt;. This means that you can install the tool in the current folder, with its own version and thus independent from other tools or versions on your machine. More information can be read &lt;a href=&quot;https://medium.com/@bilalfazlani/net-core-local-tools-are-here-fe9ac2464481&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;calling-the-installation-of-the-tool-in-azure-devops&quot;&gt;Calling the installation of the tool in Azure DevOps&lt;/h2&gt;
&lt;p&gt;To actually install the tool (locally) through the .NET Core tasks you need to run the command in a specific way. This took quite some testing to figure this out. I wish this was documented a little better, so here it is for myself in the future 😁:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190903/20190903_ToolInstall.png&quot; alt=&quot;Example of the configuration in Azure DevOps&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Do note that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;custom command&lt;/code&gt; to run is just &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tool&lt;/code&gt; and the parameter input gets the name of the action and the tool.&lt;/p&gt;

&lt;p&gt;In this case I am installing &lt;a href=&quot;https://stryker-mutator.io/stryker-net/&quot;&gt;Stryker&lt;/a&gt; to start with mutation testing.&lt;/p&gt;

&lt;h3 id=&quot;error-cannot-find-any-manifests-file&quot;&gt;Error: ‘Cannot find any manifests file’&lt;/h3&gt;
&lt;p&gt;Just running the installation in the work folder will give the error below. .NET Core wants a config file for local tools. Find more information about that &lt;a href=&quot;/blog/2019/09/03/fixing-error-.NET-core-dotnet-new-tool-manifest&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;Cannot&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;manifests&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;file.&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Searched:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;C:\Apps\TFSAgent\_work\7\s\.config\dotnet-tools.json&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;C:\Apps\TFSAgent\_work\7\s\dotnet-tools.json&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;C:\Apps\TFSAgent\_work\7\.config\dotnet-tools.json&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;C:\Apps\TFSAgent\_work\7\dotnet-tools.json&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To get a new manifest, add an extra .NET Core task and run this custom command:&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;dotnet&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;tool-manifest&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;run-the-net-core-tool&quot;&gt;Run the .NET Core Tool&lt;/h2&gt;
&lt;p&gt;After setting up a manifest and the installation itself, you can now run the .NET Core tool itself by using a custom command again:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190903/20190903_ToolRun.png&quot; alt=&quot;Running the .NET Core tool in Azure DevOps&quot; /&gt;&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Lets Encrypt: Manually get a certificate on Windows for an Azure App Service</title>
			<link href="https://devopsjournal.io/blog/2019/08/27/LetsEncrypt-Windows"/>
			<updated>2019-08-27T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2019/08/27/LetsEncrypt-Windows</id>
			<content type="html">&lt;p&gt;Recently I had to refresh a &lt;a href=&quot;https://letsencrypt.org/&quot;&gt;Let’s Encrypt&lt;/a&gt; certificate for an Azure App Service after the first certificate had expired. Of course, refreshing a certificate should be done by some tooling, either in a CI/CD pipeline or another service. I tried setting up the &lt;a href=&quot;https://github.com/sjkp/letsencrypt-siteextension/wiki/How-to-install&quot;&gt;Lets Encrypt Extension&lt;/a&gt; on the App Service, but could not get it to work. Eventually I even ran into the Let’s Encrypt rule that you can only try to get a certificate 5 times a week for a production environment, after which they blocked me. Therefor I decided to update the certificate by hand, because that should not be too hard at all…. Unfortunately this was not as straight forward as I wanted it, so I decided to document the process here for later referral when I run into this again. Hopefully I’ve remembered to have automated this the next time!&lt;/p&gt;

&lt;p&gt;There will be steps in here that can be executed easier. If you have any tips, please let me know!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190827/johnny-chen-CE1_qYPbMBU-unsplash.jpg&quot; alt=&quot;Underwater photo of a school of fish&quot; /&gt;&lt;/p&gt;
&lt;h5 id=&quot;unsplash-logophoto-by-johnny-chen&quot;&gt;&lt;a style=&quot;background-color:black;color:white;text-decoration:none;padding:4px 6px;font-family:-apple-system, BlinkMacSystemFont, &amp;quot;San Francisco&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Ubuntu, Roboto, Noto, &amp;quot;Segoe UI&amp;quot;, Arial, sans-serif;font-size:12px;font-weight:bold;line-height:1.2;display:inline-block;border-radius:3px&quot; href=&quot;https://unsplash.com/@johnnyafrica?utm_medium=referral&amp;amp;utm_campaign=photographer-credit&amp;amp;utm_content=creditBadge&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; title=&quot;Download free do whatever you want high-resolution photos from Johnny Chen&quot;&gt;&lt;span style=&quot;display:inline-block;padding:2px 3px&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; style=&quot;height:12px;width:auto;position:relative;vertical-align:middle;top:-2px;fill:white&quot; viewBox=&quot;0 0 32 32&quot;&gt;&lt;title&gt;unsplash-logo&lt;/title&gt;&lt;path d=&quot;M10 9V0h12v9H10zm12 5h10v18H0V14h10v9h12v-9z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;&lt;span style=&quot;display:inline-block;padding:2px 3px&quot;&gt;Photo by Johnny Chen&lt;/span&gt;&lt;/a&gt;&lt;/h5&gt;

&lt;h2 id=&quot;lets-encrypt-process&quot;&gt;Let’s encrypt process&lt;/h2&gt;
&lt;p&gt;To be able to get a Let’s Encrypt certificate you first need to prove that the domain you are using is actually owned by you. Most identity challenges do this by requiring a specific txt-record in the root of the domain so they can request that from the DNS server. The more modern way to do this is by setting up a specific well known route on the webserver for this specific use. It seems that the industry standard is moving to the same setup. Let’s encrypt sends a request to a sub-url on the domain you are validating to  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\.well-known\acme-challenge\unique_file_name&lt;/code&gt;.  It checks to see if a specific set of characters is in the file. If you can set that information up on the domain, it proves to Let’s Encrypt that you are the domain owner and they can generate a certificate for you.&lt;/p&gt;

&lt;h2 id=&quot;windows-subsystem-for-linux&quot;&gt;Windows Subsystem for Linux!&lt;/h2&gt;
&lt;p&gt;To get that filename and its contents you can use the Certbot, that is available for a couple of different Linux distro’s. Since I have Ubuntu running on my Windows machine inside Windows Subsystem for Linux (WSL), I wanted to use that. I followed the installation steps from the &lt;a href=&quot;https://certbot.eff.org/lets-encrypt/ubuntubionic-other&quot;&gt;documentation&lt;/a&gt;.
After the installation I can now run the certbot with:&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;certbot certonly &lt;span class=&quot;nt&quot;&gt;--manual&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The bot will then ask you a couple of questions, like the domain(s) you want to get the certificate for, your email address so they can e-mail you when the certificate is about to expire and if you are OK with logging your IP-address.
&lt;img src=&quot;/images/2019/20190827/2019-08-27_CertBot.png&quot; alt=&quot;Example of Certbot commands in WSL&quot; /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-dos&quot;&gt;IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/www.gdbc-challenges.com/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/www.gdbc-challenges.com/privkey.pem
   Your cert will expire on 2019-11-25. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot
   again. To non-interactively renew *all* of your certificates, run
   &quot;certbot renew&quot;
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let&apos;s Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;uploading-the-challenge-files-to-an-app-service&quot;&gt;Uploading the challenge files to an App Service&lt;/h2&gt;
&lt;p&gt;In the screenshot above you can see the name of the file and the contents Let’s Encrypt expects in the challenge. In the Windows Terminal I am using you can select the text with the left mouse button and then copy it to the clipboard with the right mouse button. &lt;strong&gt;Trying to do this with the keyboard will send a key to the shell and will be seen as a ‘return’ key-press!&lt;/strong&gt; When this happens, Let’s Encrypt will try to validate the file and you can start over again! Luckily it will rotate the expected file and content, so after two or three tries you will be back at the initial values 😉.&lt;/p&gt;

&lt;p&gt;Create a new file in the Azure App Service with the correct name through Kudu:
&lt;img src=&quot;/images/2019/20190827/2019-08-27_Kudu.png&quot; alt=&quot;Example of opening Kudu in the Azure Portal&quot; /&gt;
You can only do this with the editor in Kudu, since the App Service Editor will only enable you to create or edit files within the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/site/&lt;/code&gt; folder. The acme-challenge lives outside of it.&lt;/p&gt;

&lt;h2 id=&quot;extracting-the-lets-encrypt-files&quot;&gt;Extracting the Let’s Encrypt files&lt;/h2&gt;
&lt;p&gt;After Let’s Encrypt validates the domain, the CertBot will write down a couple of files that you can use for the certificate.  It will tell you that it wrote the files in the following location: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;etc\letsencrypt\live\your_domain_here&lt;/code&gt;. To get to those files from Windows, you need to find out where WSL saves its local files. The root the file store is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%userprofile%\AppData\Local\Packages&lt;/code&gt;. As I am using Ubuntu, the folder for that subsystem is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CanonicalGroupLimited.Ubuntu18.04onWindows_79rhkp1fndgsc&lt;/code&gt; from where I can navigate to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LocalState\rootfs\etc\&lt;/code&gt; to find the root file system and then the rest of the path.
&lt;img src=&quot;/images/2019/20190827/2019-08-27_ETC.png&quot; alt=&quot;Example of Certbot result files&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Be aware&lt;/strong&gt; the files in this directory only contain links to an archive folder!
&lt;img src=&quot;/images/2019/20190827/2019-08-27_Links.png&quot; alt=&quot;Links to other files in the archive folder&quot; /&gt;
Get the actual files from that path, you can see they are all suffixed with the number one in the filename.&lt;/p&gt;

&lt;h2 id=&quot;converting-the-pem-files-to-a-pfx&quot;&gt;Converting the pem files to a pfx&lt;/h2&gt;
&lt;p&gt;Azure App service wants to have a pfx file instead of the pem file that was generated. After all, it is IIS behind the covers, so it behaves the same way as IIS.  This means we need to convert the pem file to a pfx file. You can do that in several ways, but the OpenSSL tooling that was mentioned in this &lt;a href=&quot;https://stackoverflow.com/questions/808669/convert-a-cert-pem-certificate-to-a-pfx-certificate&quot;&gt;Stack Overflow question&lt;/a&gt; seemed rather straightforward. Unfortunately &lt;a href=&quot;https://www.openssl.org/source/&quot;&gt;OpenSSL&lt;/a&gt; does not provide the binaries for the different platforms anymore. You can only download the Git repository and try to build it from there. Luckily I found the binaries hosted &lt;a href=&quot;http://slproweb.com/products/Win32OpenSSL.html&quot;&gt;here&lt;/a&gt; and I used them to execute the next steps.&lt;/p&gt;

&lt;p&gt;Navigate to the OpenSSL path and execute this command to generate a pfx based from the pem files Let’s Encrypt generated:&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;\openssl&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pkcs12&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-inkey&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;C:\Users\RobBos\Desktop\GDBC Challenges\privkey1.pem&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-in&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;C:\Users\RobBos\Desktop\GDBC Challenges\fullchain1.pem&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-certfile&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;C:\Users\RobBos\Desktop\GDBC Challenges\cert1.pem&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-export&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-out&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;C:\Users\RobBos\Desktop\GDBC Challenges\gdbc_challenges.pfx&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;It will request a password that can be left empty for usage in Windows itself, but Azure App Service requires a password on it.
&lt;img src=&quot;/images/2019/20190827/2019-08-27_CovertPEM.png&quot; alt=&quot;Powershell command to convert the pem files to a pfx&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;uploading-the-new-certificate&quot;&gt;Uploading the new certificate&lt;/h2&gt;
&lt;p&gt;Uploading a certificate to Azure App Service can be done in just a few steps. Upload the new certificate and bind it with an SNI Binding to the correct domain.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190827/2019-08-27_UploadCert.png&quot; alt=&quot;Upload certificate&quot; /&gt;&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Run .NET Core programs in Azure DevOps</title>
			<link href="https://devopsjournal.io/blog/2019/08/17/AzureDevOps-Run-NET-Core"/>
			<updated>2019-08-17T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2019/08/17/AzureDevOps-Run-NET-Core</id>
			<content type="html">&lt;p&gt;Recently I wanted to build and run a .NET core console application in Azure DevOps and found out you cannot do that with the default .NET core tasks.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190817/sam-truong-dan--rF4kuvgHhU-unsplash.jpg&quot; alt=&quot;&quot; /&gt;
&lt;a style=&quot;background-color:black;color:white;text-decoration:none;padding:4px 6px;font-family:-apple-system, BlinkMacSystemFont, &amp;quot;San Francisco&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Ubuntu, Roboto, Noto, &amp;quot;Segoe UI&amp;quot;, Arial, sans-serif;font-size:12px;font-weight:bold;line-height:1.2;display:inline-block;border-radius:3px&quot; href=&quot;https://unsplash.com/@sam_truong?utm_medium=referral&amp;amp;utm_campaign=photographer-credit&amp;amp;utm_content=creditBadge&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; title=&quot;Download free do whatever you want high-resolution photos from Sam Truong Dan&quot;&gt;&lt;span style=&quot;display:inline-block;padding:2px 3px&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; style=&quot;height:12px;width:auto;position:relative;vertical-align:middle;top:-2px;fill:white&quot; viewBox=&quot;0 0 32 32&quot;&gt;&lt;title&gt;unsplash-logo&lt;/title&gt;&lt;path d=&quot;M10 9V0h12v9H10zm12 5h10v18H0V14h10v9h12v-9z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;&lt;span style=&quot;display:inline-block;padding:2px 3px&quot;&gt;Photo by Sam Truong Dan&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The default tasks in Azure DevOps and &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/pipelines/languages/dotnet-core?view=azure-devops&amp;amp;WT.mc_id=DOP-MVP-5003719&quot;&gt;tutorials&lt;/a&gt; are more geared towards web-development and publishing a zip file that can be used with a WebDeploy command.&lt;/p&gt;

&lt;p&gt;For an application,I would have thought that you could run the compiled assembly by calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotnet run path-to-assembly&lt;/code&gt; on it. Turns out that the run command is used to run the code from a project, not from a compiled assembly (see the &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-run?tabs=netcore21&amp;amp;WT.mc_id=DOP-MVP-5003719&quot;&gt;docs&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;You can just call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotnet path-to-assembly&lt;/code&gt;, but the .NET core tasks in Azure DevOps will not let you do that: you can select a custom command, but you cannot leave that command empty for example.&lt;/p&gt;

&lt;h2 id=&quot;option-1-publish-the-application-to-self-contained&quot;&gt;Option 1: Publish the application to self-contained&lt;/h2&gt;
&lt;p&gt;Here’s how to go around that limitation: publish the application for the platform(s) you want to run: that way you’ll have an executable that can be executed with a PowerShell task. I choose the Windows platform as a target and published the files to a separate &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;publish&lt;/code&gt; folder.
&lt;img src=&quot;/images/2019/20190816/20190816_06_AzureDevOpsBuild.png&quot; alt=&quot;Azure Build Pipeline overview&quot; /&gt;.&lt;/p&gt;

&lt;p&gt;You can then run it in a release.
The release just consists of extracting the build artefact, overwriting the application settings with an &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=sergeyzwezdin.magic-chunks&quot;&gt;Azure DevOps Extension&lt;/a&gt; and running the executable.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190816/20190816_06_AzureDevOpsRelease.png&quot; alt=&quot;Azure Release Pipeline Task running the executable&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;option-2-run-the-assembly&quot;&gt;Option 2: Run the assembly&lt;/h2&gt;
&lt;p&gt;An even easier way to run the assembly is to call the dotnet command on the assembly itself, just do it in a PowerShell task:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190817/20190817_01_AzDo-Run-dll.png&quot; alt=&quot;Azure Release Pipeline with Task calling the assembly&quot; /&gt;&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Azure DevOps Marketplace News - Or Imposter Syndrome for developers?</title>
			<link href="https://devopsjournal.io/blog/2019/08/16/AzDoMarketplaceNews"/>
			<updated>2019-08-16T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2019/08/16/AzDoMarketplaceNews</id>
			<content type="html">&lt;p&gt;I’ve been developing software for over 16 years now and every now and then I come across someone who thinks developers do something magic that they can never learn to do. Maybe they are even afraid to ask us something because they don’t understand something. As a consultant my role has often meant that I could take the time and explain to someone more functional oriented the reasoning behind some decisions a developer could make.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190816/clem-onojeghuo-JUHW6hAToY4-unsplash.jpg&quot; alt=&quot;Picture of shoes on a forest background&quot; /&gt;&lt;/p&gt;
&lt;h5 id=&quot;photo-by-clem-onojeghuo-on-unsplash&quot;&gt;Photo by Clem Onojeghuo on Unsplash&lt;/h5&gt;

&lt;p&gt;So this post is meant as an example battling against &lt;a href=&quot;https://en.wikipedia.org/wiki/Impostor_syndrome&quot;&gt;imposter syndrome&lt;/a&gt; and to openly document the development process and some key decisions along the way and give an insight about the stuff I need to look up (hint: &lt;strong&gt;it is a lot!&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;The git repository with the source for the tool is open source and can be found on &lt;a href=&quot;https://github.com/rajbos/azure-devops-marketplace-extension-news&quot;&gt;Github&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;All in all I have spend around 6 hours in an editor (measured with the &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=WakaTime.WakaTime&quot;&gt;WakaTime extension&lt;/a&gt;) over the course of 2 weeks to get a working twitter account that checks something every three hours and tweets about it if needed.&lt;/p&gt;

&lt;h2 id=&quot;reason-this-project-exists&quot;&gt;Reason this project exists&lt;/h2&gt;
&lt;p&gt;The reason I started with this project is that I always wondered if there could be a way to stay up to date on Azure DevOps extensions on the &lt;a href=&quot;https://marketplace.visualstudio.com/azuredevops&quot;&gt;Marketplace&lt;/a&gt;: there are many extensions available in it and if you don’t check the marketplace regularly, you can easily miss some gems. Of course, when you encounter a specific problem, you will probably find the extensions when you need them, but I thought this was a fun thing to do.&lt;/p&gt;

&lt;p&gt;Searching around did seemed to prove there isn’t a good way to stay up to date on the extensions: there is no RSS feed, Twitter bot, or any other option than to regularly check the “Most recent” feed. Since I still am a developer, I thought I could probably make something myself and that this should be a fun thing to do that should not cost to much time to make!&lt;/p&gt;

&lt;h2 id=&quot;functional-requirements&quot;&gt;Functional requirements&lt;/h2&gt;
&lt;p&gt;My own functional requirements where quite easy:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Check the marketplace data periodically for changes.&lt;/li&gt;
  &lt;li&gt;Tweet about them on a new account when found.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Seems straightforward and not that hard!&lt;/p&gt;

&lt;h2 id=&quot;research&quot;&gt;Research&lt;/h2&gt;
&lt;p&gt;To check if this is even possible with the API, I opened up the developer tools in Chrome and checked the calls that the marketplace page makes to the back-end. After finding the call that seemed to response with actual JSON data that listed all the extensions, I checked that I could make the same call in &lt;a href=&quot;https://www.getpostman.com/products&quot;&gt;Postman&lt;/a&gt; and actually get some results: that way I knew that I didn’t need to login to an API or send in some magic cookies or something.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190816/20190816_05_Postman.png&quot; alt=&quot;Postman example result&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Seeing results in Postman meant that this whole idea is feasible at all an convinced me to get started. I needed a tool around the calls, somewhere to store the current data, diff any changes and then tweet about new or updated extensions. So just a couple of components 😄.&lt;/p&gt;

&lt;h2 id=&quot;starting-point&quot;&gt;Starting point&lt;/h2&gt;
&lt;p&gt;I didn’t want to start exploring fun new technologies to get all the necessary steps working: I just wanted to be notified when there was a new extension available! Therefor I decided to play my strengths and stick with what I have been using for more than five years:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Git repository&lt;/li&gt;
  &lt;li&gt;C# with .NET core&lt;/li&gt;
  &lt;li&gt;Azure DevOps&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;first-real-commit&quot;&gt;First real commit&lt;/h2&gt;
&lt;p&gt;Starting in Visual Studio with File –&amp;gt; New –&amp;gt; .NET Core Console Application after creating a new Git repo I quickly created an outline to load the information from the API.&lt;/p&gt;

&lt;p&gt;In the &lt;a href=&quot;https://github.com/rajbos/azure-devops-marketplace-extension-news/commit/16fc389e6f86ad822dc0d42b326e7f4fb45446fd&quot;&gt;first commit&lt;/a&gt; you can see the thinking process and coding style that I use when building a MVP (minimal viable product).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190816/20190816_02_FirstRealCommit.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Everything is still in the Program class with a lot of to-do’s in it. Some data classes like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ExtensionDataResult.cs&lt;/code&gt; are separated and just filled with the awesome Visual Studio feature “Paste JSON as Classes”.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190816/20190816_03_PasteSpecial.png&quot; alt=&quot;Paste JSON as Classes&quot; /&gt;&lt;/p&gt;

&lt;p&gt;At this point I was mostly trying to get the download working and store the results in a list so I could easily start a diff method next.&lt;/p&gt;

&lt;p&gt;I haven’t found it easy to do any TDD around my code, especially not at first, and because I was the only developer I skipped all those best practices 😁. Seeing this as a fun side project supported that decision. My head still seems to work in a different way and I like to see the results as soon as possible.&lt;/p&gt;

&lt;h2 id=&quot;searching-for-code&quot;&gt;Searching for code&lt;/h2&gt;
&lt;p&gt;To give a feel for the amount of searching around I did to actually create a tool around my own needs:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Working with HttpClient to send calls to the Marketplace API: looked it up in a &lt;strong&gt;different project&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;Serializing the result with &lt;a href=&quot;https://www.nuget.org/packages/Newtonsoft.Json/&quot;&gt;Newtonsoft.Json&lt;/a&gt;: &lt;strong&gt;googled it.&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Sending a tweet through the Twitter API: found an &lt;strong&gt;example on&lt;/strong&gt; &lt;a href=&quot;https://stackoverflow.com&quot;&gt;Stack Overflow&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Setting up a developer account on Twitter so that I can tweet: googled it.&lt;/li&gt;
  &lt;li&gt;Exporting a list to CSV: &lt;strong&gt;googled it.&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Storing the data in an Azure Storage Account using blobs: looked it up in a &lt;strong&gt;different project&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;flow&quot;&gt;Flow&lt;/h2&gt;
&lt;p&gt;After getting the JSON results and de-serializing them, I wanted to see what overall information was in the dataset, if there where any duplicates and other stuff that stood out. One of the quickest ways to do this for me is in good old Excel: just export a &lt;a href=&quot;https://github.com/rajbos/azure-devops-marketplace-extension-news/blob/main/AzDoExtensionNews/AzDoExtensionNews/Helpers/CSV.cs&quot;&gt;CSV&lt;/a&gt; and load it in, create a pivot table on it and go to town. From that I learned that I did need to deduplicate the list and that there are tags that could mean that the extension was pushed to the marketplace but was not made public yet. Good checks to add to the application.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190816/20190816_01_Commits.png&quot; alt=&quot;GitHub commit history&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;running-locally&quot;&gt;Running locally&lt;/h3&gt;
&lt;p&gt;This is still a console application that I run in debug mode to see if everything works. Next step was storing the data to disk, so I could create a method to diff the lists between the previous check and the current one. When I found changes in the next day (remember that this took two weeks to complete?), I searched around for some code on how to create tweets from the changes. I found an example on Stack Overflow that looked okay and just worked, so I’ve included that in a separate &lt;a href=&quot;https://github.com/rajbos/azure-devops-marketplace-extension-news/blob/main/AzDoExtensionNews/News.Library/Twitter.cs&quot;&gt;class&lt;/a&gt;. I haven’t even added a real secret store yet: I am just running it from appSettings.json with an optional load of appSettings.secrets.json! Yes, quick and dirty 😛. I did make sure to add that file to the .gitignore before committing, so that is fine for my single developer use case.&lt;/p&gt;

&lt;p&gt;I think I ran with this setup for at least a week, running it multiple times a day, right from Visual Studio. I added logging on any exceptions that occurred, added extra information to the tweets, like the name of the publisher, using the tags as Twitter hashtags, etc.&lt;/p&gt;

&lt;p&gt;After running for a couple of days I got some feedback about some of the tags that seemed to be tags that the marketplace website uses to determine for instance if the extension is paid or if it has a trial period attached to it. That has been added in as well.&lt;/p&gt;

&lt;p&gt;I also thought it would be nice to add the publishers to the tweet themselves if I could find their Twitter accounts. The first publishers have been added to a &lt;a href=&quot;https://github.com/rajbos/azure-devops-marketplace-extension-news/blob/main/AzDoExtensionNews/AzDoExtensionNews/Data/Publishers.json&quot;&gt;hard-coded list&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;See how gradually this has progressed? Everything has been added when I actually had a need, and refactored into separate classes when those parts felt ready.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190816/20190816_04_VisualStudioExplorer.png&quot; alt=&quot;Visual Studio Explorer pane of the current solution&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;unit-tests&quot;&gt;Unit tests&lt;/h2&gt;
&lt;p&gt;Only recently I added my first unit test to the project, because I wanted to test if the Azure DevOps publish tags would work correctly: that is a comma separated string in the JSON object and I created some &lt;a href=&quot;https://github.com/rajbos/azure-devops-marketplace-extension-news/blob/main/AzDoExtensionNews/AzDoExtensionNews.UnitTests/Helpers/TagsUnitTests.cs&quot;&gt;tests&lt;/a&gt; to make sure those would work.&lt;/p&gt;

&lt;h2 id=&quot;automated-runs&quot;&gt;Automated runs&lt;/h2&gt;
&lt;p&gt;The next step was to run the tool somewhere in an automated fashion. I could do something fancy and go to a serverless solution, but in the current iteration had enough value in it for myself, so I just need it to run somewhere. I could have just added a local Windows Task that ran the tool on an interval or something, but that was not good enough: I wanted to run every couple of hours, even when my laptop was not running. I also did not want to add all kinds of exception handling in the case the tool was running and my WiFi lost its connection or the laptop was being shutdown. Basically I needed a scheduler that could run my .NET Core tool.&lt;/p&gt;

&lt;h3 id=&quot;preparation&quot;&gt;Preparation&lt;/h3&gt;
&lt;p&gt;Before I could run the tool on a different machine, I needed to make sure that the data file that I was saving locally on each run, would not interfere with a local file for the Azure DevOps Agent. Time to start storing that file in an Azure Storage Account as a blob! Only now I’ve added the code to do this. If you check the code, you can find that all I’ve changed is downloading and uploading the file from blob storage to a local copy. The rest of the code has not been changed, because it worked. No need to go to an in-memory solution without writing the file to disk, the current code works and speed is not an issue if I run once every three hours.&lt;/p&gt;

&lt;h1 id=&quot;azure-pipelines&quot;&gt;Azure Pipelines&lt;/h1&gt;
&lt;p&gt;As a consultant I currently live and breathe Azure DevOps on a daily basis, so this tool is my big scheduling hammer: I don’t need any other fancy stuff, just run it every three hours and let me know if something happens. I have an MSDN account that I can use for these test projects and as Azure DevOps provides unlimited pipeline minutes on open source projects, this fits perfectly.&lt;/p&gt;

&lt;h2 id=&quot;building-the-solution&quot;&gt;Building the solution&lt;/h2&gt;
&lt;p&gt;The first step is building the solution. I could run it from the build but that seems like overkill: the solution should not change that much that often (after the first few iterations) and adding a release for something in .NET should not be that much work.&lt;/p&gt;

&lt;p&gt;I started with the default ASP.NET core web template. That uses the flag &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Publish Web Projects&lt;/code&gt; to publish a zip file with a WebDeploy package in it. Since we cannot use that I changed the publish step.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190816/20190816_06_AzureDevOpsBuild.png&quot; alt=&quot;Azure Build Pipeline overview&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Later on I found out that the release cannot handle the normal .NET core DLL that is generated instead of a .NET executable: the .NET core tasks do not support executing the dll with the .NET &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;run&lt;/code&gt; command: it wants the .NET core project folder to first build and then run the solution. I had to work around it by publishing a full .NET core app targeting the Windows platform. That way I have an executable that I can trigger with a PowerShell task.&lt;/p&gt;

&lt;h2 id=&quot;release-or-run-pipeline&quot;&gt;Release or run pipeline&lt;/h2&gt;
&lt;p&gt;The release just consists of extracting the build artefact, overwriting the application settings with an &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=sergeyzwezdin.magic-chunks&quot;&gt;Azure DevOps Extension&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190816/20190816_06_AzureDevOpsRelease.png&quot; alt=&quot;Azure Release Pipeline&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;full-circle&quot;&gt;Full circle&lt;/h2&gt;
&lt;p&gt;I started out with a Git repo, pushed that to &lt;a href=&quot;https://github.com/rajbos/azure-devops-marketplace-extension-news&quot;&gt;GitHub&lt;/a&gt;, build and run in Azure DevOps and then report the status of the build and release through badges in Azure DevOps and included those in the readme of the repository:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Step&lt;/th&gt;
      &lt;th&gt;Latest execution&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Build&lt;/td&gt;
      &lt;td&gt;&lt;img src=&quot;https://dev.azure.com/raj-bos/AzDo%20Marketplace%20News/_apis/build/status/AzDo%20Marketplace%20News-CI&quot; alt=&quot;Build status&quot; /&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Release/Run status&lt;/td&gt;
      &lt;td&gt;&lt;img src=&quot;https://vsrm.dev.azure.com/raj-bos/_apis/public/Release/badge/301c7ef0-13c9-491b-b16d-cd07a6ec02ef/1/1&quot; alt=&quot;Release status&quot; /&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;By the way, I am using &lt;a href=&quot;/blog/2018/09/10/GitHub-Azure-DevOps-Pipeline&quot;&gt;Azure Pipelines from the GitHub marketplace&lt;/a&gt; to run the CI/CD triggers for the project.&lt;/p&gt;

&lt;p&gt;For the scheduling part I am using a scheduled trigger that will run the release definition every three hours. Somewhat irksome to add so many (no cron notation), but if it works….&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I hope this gives an insight to the development process to someone that is curious and maybe helps to lower some of the &lt;a href=&quot;https://en.wikipedia.org/wiki/Impostor_syndrome&quot;&gt;imposter syndrome&lt;/a&gt; that some developers seem to have (myself included!). With a good mindset you can figure things out or just reach out and ask for help. With that, you can get anything done!&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Using Chrome Personas to split identities</title>
			<link href="https://devopsjournal.io/blog/2019/07/13/Using-Chrome-Personas-To-Split-Identities"/>
			<updated>2019-07-13T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2019/07/13/Using-Chrome-Personas-To-Split-Identities</id>
			<content type="html">&lt;p&gt;As a consultant, I get to work at a lot of different settings and environments. For most of my customers these days, that means working on my own laptop and in the cloud with SaaS application.&lt;/p&gt;

&lt;p&gt;Logging in to all those customers can be a messy thing: I’ve seen people having a identity picker in Azure (or any other Azure Active Directory backed system) that they have to &lt;strong&gt;scroll&lt;/strong&gt; through to get to the identity they want to use 😱. Seriously!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: It did get better in the last years, previously you actually had to log out and in as a different identity to make this work, but still… we can do this better!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190713/jesse-orrico-unsplash.jpg&quot; alt=&quot;Hero image&quot; /&gt;&lt;/p&gt;
&lt;h5 id=&quot;unsplash-logojesse-orrico-on-unsplash&quot;&gt;&lt;a style=&quot;background-color:black;color:white;text-decoration:none;padding:4px 6px;font-family:-apple-system, BlinkMacSystemFont, &amp;quot;San Francisco&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Ubuntu, Roboto, Noto, &amp;quot;Segoe UI&amp;quot;, Arial, sans-serif;font-size:12px;font-weight:bold;line-height:1.2;display:inline-block;border-radius:3px&quot; href=&quot;https://unsplash.com/@jessedo81?utm_medium=referral&amp;amp;utm_campaign=photographer-credit&amp;amp;utm_content=creditBadge&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; title=&quot;Download free do whatever you want high-resolution photos from jesse orrico&quot;&gt;&lt;span style=&quot;display:inline-block;padding:2px 3px&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; style=&quot;height:12px;width:auto;position:relative;vertical-align:middle;top:-2px;fill:white&quot; viewBox=&quot;0 0 32 32&quot;&gt;&lt;title&gt;unsplash-logo&lt;/title&gt;&lt;path d=&quot;M10 9V0h12v9H10zm12 5h10v18H0V14h10v9h12v-9z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;&lt;span style=&quot;display:inline-block;padding:2px 3px&quot;&gt;Jesse Orrico on Unsplash&lt;/span&gt;&lt;/a&gt;&lt;/h5&gt;

&lt;h2 id=&quot;solution&quot;&gt;Solution&lt;/h2&gt;

&lt;p&gt;I have a helpful way of keeping all those personas separately from each other, with some additional benefits.&lt;/p&gt;

&lt;p&gt;First and foremost, I do this to keep everything nice and tidy: separating them helps my mind in compartmentalizing the status and context of the tasks that I am doing.&lt;/p&gt;

&lt;h1 id=&quot;chrome-personas&quot;&gt;Chrome Personas&lt;/h1&gt;
&lt;p&gt;I have use Chrome personas for several years to accomplish this, after learning this feature was available in FireFox and Google had copied that functionality. It is a feature that lets you separate parts of your browser into it’s own (hosted) process and keep everything in that compartment. I noticed that Chrome does this with:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Browsing history&lt;/li&gt;
  &lt;li&gt;Tab history&lt;/li&gt;
  &lt;li&gt;Plugins&lt;/li&gt;
  &lt;li&gt;Stored Passwords&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;benefits&quot;&gt;Benefits&lt;/h3&gt;
&lt;p&gt;So by doing this, I can separate my personas and with it my identity! I make a new persona for every customer that I get to (and others for personal account separation). I let Chrome store the URLs, tabs and passwords that I need for that customer, and when I leave, I just remove the persona from my system!&lt;/p&gt;

&lt;p&gt;In each persona I have logged in to different Azure accounts for example, together with different Azure DevOps accounts, Office365 logins and other services that I need (tooling, CRM’s, other SaaS offerings). This saves a lot of switching.&lt;/p&gt;

&lt;p&gt;Some note’s to this:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;I don’t sync the personas currently, as I mainly use my 1 laptop), but you can if you want to. This is a persona by persona setting.&lt;/li&gt;
  &lt;li&gt;Any account information that I need for later is stored inside of a &lt;a href=&quot;https://keepass.info/&quot;&gt;KeePass&lt;/a&gt; file backed up in my Office365 OneDrive folder.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;cool-now-how-do-you-do-this&quot;&gt;Cool! Now how do you do this?&lt;/h2&gt;
&lt;p&gt;To get started, click on the User circle on the top right. You get a flyout with all you persona’s. Mine is quite long:
&lt;img src=&quot;/images/2019/20190713/20190713_01_MyPersonas.png&quot; alt=&quot;Persona&apos;s fly out in Chrome&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Noticed the persona &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Faebook&lt;/code&gt;? I wanted a persona with my FaceBook account in it, to prevent my personal account leaking around everywhere when I don’t want to. I use that product only on my phone for family stuff, so I did not want to login to it on any of my persona’s. Unfortunately &lt;strong&gt;you cannot renamed&lt;/strong&gt; them (can’t find it in the UI anyway)!&lt;/p&gt;

&lt;h3 id=&quot;manage-people&quot;&gt;Manage people&lt;/h3&gt;

&lt;p&gt;Click on ‘manage people’ to open a window where you can create new accounts and delete existing ones:
&lt;img src=&quot;/images/2019/20190713/20190713_03_PersonasAdmin.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Notice that you can use a lot of different icons and images to link to your persona. I usually use the companies color for my customers persona and something else for personal stuff.&lt;/p&gt;

&lt;p&gt;Now you can use the new persona. Notice how it opens a new Chrome window that’s also separated on the taskbar? That means you can pin it!&lt;/p&gt;

&lt;p&gt;My most active accounts are always within reach.
&lt;img src=&quot;/images/2019/20190713/20190713_02_Taskbar.png&quot; alt=&quot;Windows Taskbar with the different personas&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;edge-dev&quot;&gt;Edge Dev&lt;/h2&gt;
&lt;p&gt;You can do the same thing in &lt;a href=&quot;https://developer.microsoft.com/en-us/microsoft-edge/?WT.mc_id=DOP-MVP-5003719&quot;&gt;Edge Dev&lt;/a&gt;, since the functionality is in the &lt;a href=&quot;https://www.chromium.org/Home&quot;&gt;Chromium&lt;/a&gt; underpinnings that both Edge Dev and Chrome build on top off.&lt;/p&gt;

&lt;p&gt;I wanted to use Edge Dev for this because of one big benefit:
&lt;img src=&quot;/images/2019/20190713/20190713_04_OpenAsInChrEdge.png&quot; alt=&quot;Open link in different personas in Edge Dev&quot; /&gt;
This means that you no longer have to copy and paste links into a different browser/persona window to use a link! You can just use the build in functionality.&lt;/p&gt;

&lt;p&gt;The main reason I haven’t started moving my personas there is listed below in the Downside section below. I was a little disappointed by this and got sidetracked with other stuff. Writing about this is getting me to think about doing it again…&lt;/p&gt;

&lt;h2 id=&quot;downside&quot;&gt;Downside&lt;/h2&gt;
&lt;p&gt;The only real downside I have with this setup, is that Chrome does not let me &lt;strong&gt;pin my main persona&lt;/strong&gt; (the one that I did not create) to the taskbar. Edge Dev has the same issue, so I suspect that this stems from the Chromium underpinnings.&lt;/p&gt;

&lt;h3 id=&quot;main-persona&quot;&gt;Main persona&lt;/h3&gt;
&lt;p&gt;The main persona seems to be the one that you logged in with to Chrome: I use that as my Gmail account and let that persona sync across other devices (iPad and iPhone). It is the user account on Googles back-end, so to say.&lt;/p&gt;

&lt;h3 id=&quot;the-issue-with-the-main-persona&quot;&gt;The issue with the main persona&lt;/h3&gt;
&lt;p&gt;The main persona is attached to the first Chrome pin on the taskbar. When you switch to that persona in Chrome, suddenly it is updated with the image for that persona:
&lt;img src=&quot;/images/2019/20190713/20190713_05_Taskbar2.png&quot; alt=&quot;Taskbar with main persona icon filled in&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Note the difference with the screenshot before, when that persona was not loaded. Notice the 6th icon here? It was empty before:
&lt;img src=&quot;/images/2019/20190713/20190713_02_Taskbar.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When you try to pin that persona, it’s pinned as the default process! There is no way to have that persona pinned on the taskbar. If you close that session, the icon is clear again. If you click on it &lt;strong&gt;it will open the last opened persona&lt;/strong&gt;!&lt;/p&gt;

&lt;h2 id=&quot;help-me-out&quot;&gt;Help me out&lt;/h2&gt;
&lt;p&gt;If you have a way to fix this (If have even tried to mess around in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;C:\Users\RobBos\AppData\Roaming\Microsoft\Internet Explorer\Quick Launch\User Pinned\&lt;/code&gt; with manual shortcuts, but none of it worked), please reach out!&lt;/p&gt;

&lt;p&gt;That would solve the only issue that I have with this setup: it could be complete by fixing this!&lt;/p&gt;

&lt;h2 id=&quot;update-also-tested-with-launching-chrome-manually&quot;&gt;Update: also tested with launching Chrome manually&lt;/h2&gt;
&lt;p&gt;After a tip from &lt;a href=&quot;https://twitter.com/jaspergilhuis&quot;&gt;Jasper&lt;/a&gt; I tried to see if I can launch Chrome.exe from the commandline with the correct persona. You can do so by providing it an extra parameter:
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.\chrome.exe --profile-directory=&quot;Profile 1&quot; &lt;/code&gt;
Maybe this can be included in a shortcut and launch it that way?&lt;/p&gt;

&lt;p&gt;To see how Chrome names the personas, you can check your user directory here: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;C:\Users\RobBos\AppData\Local\Google\Chrome\User Data\Guest Profile&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Or look in the registry editor:
&lt;img src=&quot;/images/2019/20190713/20190713_06_ChromeManually.png&quot; alt=&quot;Registry editor window with Chrome settings open&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I’ve tried them all before finding that the Default profile is indeed called…. the Default profile! Unfortunately does the same thing.
I also found to extra personas that might have been deleted at some point. After launching them, they are also visible again in the Chrome UI!&lt;/p&gt;

&lt;p&gt;By the way, storing the shortcut like this on the desktop and launching it does work. But I don’t like to switch to the desktop each time I want to launch into this profile.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Fixing GitHub Pages Syntax Highlighting</title>
			<link href="https://devopsjournal.io/blog/2019/07/12/Github-Pages-Syntax-Highlighting"/>
			<updated>2019-07-12T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2019/07/12/Github-Pages-Syntax-Highlighting</id>
			<content type="html">&lt;p&gt;Today I noticed that my syntax highlighting was not working on this blog. Here is how I fixed it!&lt;/p&gt;

&lt;p&gt;I am using Jekyll on GitHub pages as I wrote &lt;a href=&quot;/blog/2017/12/17/trying-out-jekyll-on-github-pages&quot;&gt;before&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190712.02/zach-reiner-unsplash.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;unsplash-logophoto-by-zach-reiner&quot;&gt;&lt;a style=&quot;background-color:black;color:white;text-decoration:none;padding:4px 6px;font-family:-apple-system, BlinkMacSystemFont, &amp;quot;San Francisco&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Ubuntu, Roboto, Noto, &amp;quot;Segoe UI&amp;quot;, Arial, sans-serif;font-size:12px;font-weight:bold;line-height:1.2;display:inline-block;border-radius:3px&quot; href=&quot;https://unsplash.com/@_zachreiner_?utm_medium=referral&amp;amp;utm_campaign=photographer-credit&amp;amp;utm_content=creditBadge&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; title=&quot;Download free do whatever you want high-resolution photos from Zach Reiner&quot;&gt;&lt;span style=&quot;display:inline-block;padding:2px 3px&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; style=&quot;height:12px;width:auto;position:relative;vertical-align:middle;top:-2px;fill:white&quot; viewBox=&quot;0 0 32 32&quot;&gt;&lt;title&gt;unsplash-logo&lt;/title&gt;&lt;path d=&quot;M10 9V0h12v9H10zm12 5h10v18H0V14h10v9h12v-9z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;&lt;span style=&quot;display:inline-block;padding:2px 3px&quot;&gt;Photo by Zach Reiner&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;Looking at the generated HTML indicated that there was some parsing done during the build of the page, but there were no CSS classes available to them:
&lt;img src=&quot;/images/2019/20190712.02/20190712_02.png&quot; alt=&quot;Showing correctly generated HTML with extra tags&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I tried searching for documentation about this issue and found some &lt;a href=&quot;https://help.github.com/en/articles/page-build-failed-invalid-highlighter-language&quot;&gt;basic stuff&lt;/a&gt;.
This hinted that I needed to set up a highlighter in my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_config.yml&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;language-yml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;highlighter&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;rouge&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;There is another highlighter (&lt;a href=&quot;http://pygments.org/&quot;&gt;Pygments&lt;/a&gt;), but this is not supported. It even seems that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rouge&lt;/code&gt; is just the default, so you do not need to set it at all!&lt;/p&gt;

&lt;p&gt;I found a &lt;a href=&quot;https://stackoverflow.com/questions/42188235/jekyll-github-pages-syntax-highlighting-not-working&quot;&gt;Stack Overflow question&lt;/a&gt; that indicated I needed to include a CSS file with the highlighting I want myself.&lt;/p&gt;

&lt;p&gt;Lazy as I am, I searched around and found a &lt;a href=&quot;https://gist.github.com/&quot;&gt;gist&lt;/a&gt; with a SCSS setup in it. I modified that to be just CSS and added it to my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;head.html&lt;/code&gt; like so:&lt;/p&gt;
&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;link&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/css/syntax.css&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rel=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;stylesheet&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I also needed to add the syntax name that I am using in lowercase to get it all to work: so &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;powershell&lt;/code&gt; instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PowerShell&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;cool-feature-of-github-pages&quot;&gt;Cool feature of GitHub Pages&lt;/h3&gt;
&lt;p&gt;GitHub pages is already cool by itself, but did you now they actually send you an e-mail if there is an issue with you setup that prevents the yml-build from working?
&lt;img src=&quot;/images/2019/20190712.01/20190712_01.png&quot; alt=&quot;E-mail error from GitHub with Page Build Warning&quot; /&gt;&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Using Azure CLI with PowerShell: error handling explained</title>
			<link href="https://devopsjournal.io/blog/2019/07/12/Azure-CLI-PowerShell"/>
			<updated>2019-07-12T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2019/07/12/Azure-CLI-PowerShell</id>
			<content type="html">&lt;p&gt;I found myself searching the internet again on how to use the Azure CLI from PowerShell so that I can use it in Azure Pipelines to create new Azure resources. The reason I want to do this with PowerShell is twofold:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Azure Pipelines has a task for using the Azure CLI, but this only has the options to use the command line (.cmd or .com files), or from bash (.sh). I don’t like them that much, I want to use PowerShell (Personal preference)!&lt;/li&gt;
  &lt;li&gt;Running the Azure CLI from PowerShell has the issue that it was not created specifically for use with PowerShell. You’ll need to do some extra work.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I’ve fixed it before, but it took a while to find it again. That is why I am documenting it here, to save me some yak shaving in the future.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190712/Yak.jpg&quot; alt=&quot;Yak to shave&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-issue-in-powershell&quot;&gt;The issue in PowerShell&lt;/h2&gt;
&lt;p&gt;Running this Azure CLI command in PowerShell will result in an error, because storage accounts cannot have a dash or capitals in the name:&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$StorageAccountName&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;testRb-001&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$ResourceGroup&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;myResourceGroup&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$location&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;westeurope&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;az&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;storage&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;account&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$StorageAccountName&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$ResourceGroup&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-l&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$location&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--sku&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Standard_LRS&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Result:
&lt;img src=&quot;/images/2019/20190712/20190712_02_Error.png&quot; alt=&quot;Error displayed in PowerShell&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Seems like an error, what’s the issue then?
Well, adding error handling like you’d expect from PowerShell will not work!&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$StorageAccountName&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;testRb-001&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$ResourceGroup&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;myResourceGroup&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$location&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;westeurope&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;az&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;storage&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;account&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$StorageAccountName&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$ResourceGroup&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-l&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$location&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--sku&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Standard_LRS&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Just continues&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;An error occurred!&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can see that PowerShell doesn’t notice the error and just continues:
&lt;img src=&quot;/images/2019/20190712/20190712_03_ErrorHandling.png&quot; alt=&quot;Error handling will not do anything with the error&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Even adding -ErrorAction will not work.&lt;/p&gt;

&lt;h2 id=&quot;how-to-add-error-handling-yourself&quot;&gt;How to add error handling yourself&lt;/h2&gt;
&lt;p&gt;The Azure CLI runs on JSON: it will try to give you JSON results on every call, so we can use that to see if we got any data back from the call. After converting the result, we can test to see if it has data:&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$StorageAccountName&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;testRb-001&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$ResourceGroup&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;myResourceGroup&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$location&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;westeurope&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$output&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;az&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;storage&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;account&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$StorageAccountName&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$ResourceGroup&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-l&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$location&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--sku&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Standard_LRS&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ConvertFrom-Json&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Error&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Error creating storage account&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Do remember to wrap &lt;strong&gt;every&lt;/strong&gt; call you need to run with this setup, and return to prevent PowerShell to continue with the next statement.&lt;/p&gt;

&lt;p&gt;Writing the error to the output helps with:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Displaying the error correctly&lt;/li&gt;
  &lt;li&gt;Blocking the release in Azure DevOps, which is were I needed this the most.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190712/20190712_04_ErrorHandlingCorrectly.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;shorthand&quot;&gt;Shorthand&lt;/h3&gt;
&lt;p&gt;There is a shorthand version of this code that you can use, if you don’t care about the information in the output (Thanks &lt;a href=&quot;https://twitter.com/duracellko&quot;&gt;Rasťo&lt;/a&gt; !). You can use PowerShells &lt;a href=&quot;https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_automatic_variables?view=powershell-5.1&quot;&gt;about automatic variable&lt;/a&gt;: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$?&lt;/code&gt; to just check if the result was successful or not.&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$StorageAccountName&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;testRb-001&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$ResourceGroup&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;myResourceGroup&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$location&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;westeurope&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$output&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;az&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;storage&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;account&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$StorageAccountName&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$ResourceGroup&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-l&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$location&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--sku&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Standard_LRS&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ConvertFrom-Json&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;$LastExitCode&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;$?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Error&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Error creating storage account&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As stated in the documentation:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Contains the execution status of the last operation. It contains TRUE if the last operation succeeded and FALSE if it failed.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;I am just not sure yet as to why this works. I suspect &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$?&lt;/code&gt; is looking at the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$LastExitCode&lt;/code&gt;, because this will print out &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$StorageAccountName&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;testRb-001&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$ResourceGroup&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;myResourceGroup&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$location&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;westeurope&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$output&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;az&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;storage&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;account&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$StorageAccountName&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$ResourceGroup&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-l&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$location&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--sku&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Standard_LRS&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ConvertFrom-Json&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;$LastExitCode&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;why-am-i-using-the-azure-cli&quot;&gt;Why am I using the Azure CLI?&lt;/h3&gt;
&lt;p&gt;After posting this, I got asked why I am using the CLI to do this at all? Surely, Azure PowerShell or ARM Templates would be sufficient.&lt;/p&gt;

&lt;p&gt;Here is why:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Azure PowerShell is not idempotent, so not so great to use in Azure Pipelines.&lt;/li&gt;
  &lt;li&gt;CLI is much terser than ARM, although it feels like you need to do a little more work, linking resources together.&lt;/li&gt;
  &lt;li&gt;Read &lt;a href=&quot;https://twitter.com/pascalnaber&quot;&gt;Pascal Naber&lt;/a&gt; ‘s post: &lt;a href=&quot;https://pascalnaber.wordpress.com/2018/11/11/stop-using-arm-templates-use-the-azure-cli-instead/&quot;&gt;Stop using ARM templates! Use the Azure CLI instead&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;I am looking into doing this with &lt;a href=&quot;https://www.terraform.io/&quot;&gt;Terraform&lt;/a&gt;, because the declaration is a lot shorter as well. It is a new paradigm to learn, and needs installation in your Azure Pipeline. The CLI was already available.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;why-not-use-bash&quot;&gt;Why not use bash?&lt;/h2&gt;
&lt;p&gt;The reasons for not using bash is that:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;It will not work on a Windows Azure Pipelines Agent (and that is what I am using here).&lt;/li&gt;
  &lt;li&gt;You need to include &lt;a href=&quot;https://stedolan.github.io/jq/&quot;&gt;JQ&lt;/a&gt; as a library to be able to parse the JSON. This seems like extra work to me. I also find the JQ syntax not that straightforward.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is a shell example to make sure you are connected to the correct &lt;a href=&quot;https://azure.com&quot;&gt;Azure&lt;/a&gt; Subscription, to be complete:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Switch to the correct subscription&lt;/span&gt;
az account &lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--subscription&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;SUBSCRIPTION_ID&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;az account show | jq &lt;span class=&quot;s1&quot;&gt;&apos;.&apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;[[&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-z&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$output&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;printf&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;FAILURE&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Error using subscriptionId, halting execution&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;NEUTRAL&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;exit &lt;/span&gt;1

&lt;span class=&quot;nv&quot;&gt;subscriptionId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$output&lt;/span&gt; | jq &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;.id&apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
		</entry>
	
		<entry>
			<title>DevOps Principles series</title>
			<link href="https://devopsjournal.io/blog/2019/07/10/DevOps-Principles-series"/>
			<updated>2019-07-10T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2019/07/10/DevOps-Principles-series</id>
			<content type="html">&lt;p&gt;I have started recording a series of video’s explaining some of the principles and sayings I use when I talk about DevOps. There are teams that I meet that have no idea what DevOps is and why we are doing some of these things for it. I’ve found myself referencing something like ‘Shift Left’ and then having to explain it to the teams and their management that I am helping with something that for me, is related to something in the DevOps culture.&lt;/p&gt;

&lt;p&gt;I also want share that information with the rest of the team members that weren’t there at that time. So for these cases, I thought it would be nice to have a set of small video’s available that I can share and explain those things to everyone.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190710/20190710_01_DevOps.png&quot; alt=&quot;DevOps Continues Cycle&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Creating these video’s helps me getting a little more comfortable speaking to an audience, even if it is through a camera lens and with the ability to stop and do it again 😄. It also helps that I have some time to think through the message I’m trying to explain to the team. From there I am trying to get my intonation and articulation under control. This stuff is all new to me, so I am figuring this all out live on the job. I’m learning tons of stuff I didn’t really think about before. This will be explained below. Somehow I like to challenge myself and do this stuff out in the open 😁.&lt;/p&gt;

&lt;p&gt;I have a different post explaining my setup and learning process on recording: &lt;a href=&quot;/blog/2019/07/10/DevOps-Principles-series-recording-setup&quot;&gt;link&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can find the entire playlist &lt;a href=&quot;https://www.youtube.com/watch?v=eEB9h8mU8rY&amp;amp;list=PLXVVwOM8uv2wQyhQ7mB_Nv_iXyMuXf-GT&quot;&gt;here&lt;/a&gt;. I try to keep them as short as possible: bite-size!&lt;/p&gt;

&lt;p&gt;Some of the topics explained are:&lt;/p&gt;
&lt;h2 id=&quot;what-is-devops&quot;&gt;What is DevOps?&lt;/h2&gt;
&lt;p&gt;I couldn’t just start and not talk about the origination and basic idea’s behind DevOps. This is a starting point for the series so I took a little extra time to explain why I started creating these video’s. You can find this episode &lt;a href=&quot;https://www.youtube.com/watch?v=eEB9h8mU8rY&amp;amp;list=PLXVVwOM8uv2wQyhQ7mB_Nv_iXyMuXf-GT&amp;amp;index=1&quot;&gt;here&lt;/a&gt; or watch it below.&lt;/p&gt;
&lt;iframe width=&quot;900&quot; height=&quot;506&quot; src=&quot;https://www.youtube.com/embed/eEB9h8mU8rY?list=PLXVVwOM8uv2wQyhQ7mB_Nv_iXyMuXf-GT&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;shift-left&quot;&gt;Shift Left&lt;/h2&gt;
&lt;p&gt;The first principle I explained is ‘Shift Left’, a common theme when we talk about DevOps. The basic goal here is to have a fast feedback cycle on anything in the development process. Watch this episode &lt;a href=&quot;https://www.youtube.com/watch?v=E4UD1dloNM8&amp;amp;list=PLXVVwOM8uv2wQyhQ7mB_Nv_iXyMuXf-GT&amp;amp;index=2&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;t-shaped-engineers&quot;&gt;T-Shaped engineers&lt;/h2&gt;
&lt;p&gt;In this episode I explained the reasoning behind ‘T-Shaped engineers’, because we move from traditional role based work like ‘developer’, ‘tester’, ‘systems engineer’ to a more generic ‘engineer’: someone with a broad skill sets in multiple aspects of the traditional roles and deep knowledge about one (or more) of them. Find the episode &lt;a href=&quot;https://www.youtube.com/watch?v=E4UD1dloNM8&amp;amp;list=PLXVVwOM8uv2wQyhQ7mB_Nv_iXyMuXf-GT&amp;amp;index=3&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;if-it-hurts-do-it-more-often&quot;&gt;If it hurts, do it more often&lt;/h2&gt;
&lt;p&gt;On of the DevOps things that I say a lot: if it hurts, do it more often! If you only update production twice a year, it will be a scary activity to do: lot’s of things can go wrong! To make this activity a non-event, we need to do it as often as we can. We will learn how to do it and we can fix the things that go wrong and improve on them. Watch the &lt;a href=&quot;https://www.youtube.com/watch?v=E4UD1dloNM8&amp;amp;list=PLXVVwOM8uv2wQyhQ7mB_Nv_iXyMuXf-GT&amp;amp;index=4&quot;&gt;video&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;you-build-it-you-run-it&quot;&gt;You build it, you run it&lt;/h2&gt;
&lt;p&gt;If you follow this principle, running the software in production becomes a team effort. Instead of throwing the software over a wall, you handle it as a team. This also means setting up monitoring and alerts and thinking about observability during the development process. Find this video in the &lt;a href=&quot;https://www.youtube.com/watch?v=E4UD1dloNM8&amp;amp;list=PLXVVwOM8uv2wQyhQ7mB_Nv_iXyMuXf-GT&amp;amp;index=5&quot;&gt;link&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;four-eyes-principle&quot;&gt;Four eyes principle&lt;/h2&gt;
&lt;p&gt;Making sure you work on the software and embed the knowledge about it in the team can be done by following the &lt;a href=&quot;https://www.youtube.com/watch?v=E4UD1dloNM8&amp;amp;list=PLXVVwOM8uv2wQyhQ7mB_Nv_iXyMuXf-GT&amp;amp;index=6&quot;&gt;four eyes principle&lt;/a&gt;. This makes sure that no-one is working on things by themselves. This will help on other things as well, like the team spirit for example.&lt;/p&gt;

&lt;h2 id=&quot;automate-everything&quot;&gt;Automate everything&lt;/h2&gt;
&lt;p&gt;Automating everything is a bit large as a saying. It’s not about automating everything in an effort to just blindly automate &lt;em&gt;everything&lt;/em&gt;: it’s about thinking of automating every step in the development process: could we benefit if we automate this step and is it worth the time we take to automate it? Watch the &lt;a href=&quot;https://www.youtube.com/watch?v=E4UD1dloNM8&amp;amp;list=PLXVVwOM8uv2wQyhQ7mB_Nv_iXyMuXf-GT&amp;amp;index=7&quot;&gt;video&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id=&quot;final-thoughts&quot;&gt;Final thoughts&lt;/h1&gt;
&lt;p&gt;Creating these video’s and sharing them online has been an interesting journey, I learn something new every time 😄.&lt;/p&gt;

&lt;p&gt;If there are any topics that you feel &lt;strong&gt;have&lt;/strong&gt; to be included, please let me know!&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>DevOps Principles series - Recording setup </title>
			<link href="https://devopsjournal.io/blog/2019/07/10/DevOps-Principles-series-recording-setup"/>
			<updated>2019-07-10T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2019/07/10/DevOps-Principles-series-recording-setup</id>
			<content type="html">&lt;p&gt;Read more on why I created short video’s wherein I explain some of the DevOps principles and practices &lt;a href=&quot;/blog/2019/07/10/DevOps-Principles-series&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190710/20190710_01_DevOps.png&quot; alt=&quot;DevOps Continues Cycle&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Creating these video’s helps me getting a little more comfortable speaking to an audience, even if it is through a camera lens and with the ability to stop and do it again 😄. It also helps that I have some time to think through the message I’m trying to explain to the team. From there I am trying to get my intonation and articulation under control. This stuff is all new to me, so I am figuring this all out live on the job. I’m learning tons of stuff I didn’t really think about before. This will be explained below. Somehow I like to challenge myself and do this stuff out in the open 😁.&lt;/p&gt;

&lt;h2 id=&quot;setup&quot;&gt;Setup&lt;/h2&gt;
&lt;p&gt;Let’s start with the basic setup. As I am trying to find out how this all works and where to go next, I am using the hard- and software I have available. As a laptop I happen to have a Dell XPS 15. The XPS range has a couple of issues in their design. The biggest one relevant to this post is the position of the webcam. Apparently the good people at Dell found the slim bezel on the top and sides of the screen more important than the actual usability of the webcam. So &lt;em&gt;up the nose shots&lt;/em&gt; it will be, unless you try to overcome that (more on that later).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190710/20190710_Webcam.jpg&quot; alt=&quot;Webcam position lower left corner of the screen&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;software&quot;&gt;Software&lt;/h2&gt;
&lt;p&gt;I’ve tried a couple of different software downloads that are freely available, for example with a trial version. I know a couple of colleagues use &lt;a href=&quot;https://www.techsmith.com&quot;&gt;Camtasia&lt;/a&gt; for this stuff, but I am not ready to commit to a € 260+ yearly purchase for a small series of video’s. Surely there must be something available that is sufficient for my objectives?&lt;/p&gt;

&lt;p&gt;Windows 10 already has some options build in, like the default camera application. Unfortunately that will not let you record  the screen as well, so only audio and the video through your webcam. What I want to achieve, is a PowerPoint presentation, with audio and my webcam in the lower right corner, taking about the subject at hand.&lt;/p&gt;

&lt;p&gt;I once came across &lt;a href=&quot;https://wistia.com/soapbox&quot;&gt;Soapbox&lt;/a&gt;, a Chrome plugin that can record the screen and impose your webcam video on top of it. This seemed like a nice solution, except that I found out that you need to pay to export the video! My goal is to create a playlist of all the video’s on my YouTube account and then share the video’s as needed. I am all for paying for the product if it adds value, yet I find that I am not ready to commit to anything yet.&lt;/p&gt;

&lt;h2 id=&quot;powerpoint&quot;&gt;PowerPoint!&lt;/h2&gt;
&lt;p&gt;Finally I came across a blog post that had a top 10 of video tools on Windows and it mentioned that PowerPoint has some functionality built in to record yourself while giving a presentation. This is to help the presenter with their timings, articulation and other things they need to see while rehearsing the presentation. Sounds like a good option, so I went ahead with it!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190710/20190710_02_PowerPoint.png&quot; alt=&quot;PowerPoint Slide Show Menu&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;height&quot;&gt;Height&lt;/h2&gt;
&lt;p&gt;Trying to prevent the weird shots, I’ve been testing with different setups. The goal is to find a setup where the camera is around my eye level and where I can sit comfortably talking over a presentation.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190710/20190710_Setup.jpg&quot; alt=&quot;Setup on desk with a pile of books underneath the laptop&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Next up is trying out to record the videos while trying to look AT the camera.&lt;/p&gt;

&lt;h2 id=&quot;light&quot;&gt;Light&lt;/h2&gt;
&lt;p&gt;I found out that light in your recording setup is really important, especially if you use the in-laptop webcam for it. More light is better! I tried recording with all the lights on in my living room, in front of a window and behind a large lamp in the kitchen. None of these were adequate enough for me: I need to invest in a good camera that I can freely position on the top of my laptop for a better view.&lt;/p&gt;

&lt;h2 id=&quot;sound&quot;&gt;Sound&lt;/h2&gt;
&lt;p&gt;For sound, I recorded the first videos with the onboard microphone. You can hear the poor quality in the first three videos! As different options, I’ve used my iPhone headset (better), my Sony WH-1000XM2 Headset (bluetooth of the Dell XPS is crap, it flunks out after 30 seconds or so) and finally got my hands on a &lt;a href=&quot;https://www.coolblue.nl/en/product/816891/trust-mantis-gxt232-streaming-microphone.html&quot;&gt;Trust Mantis GXT232 Streaming Microphone&lt;/a&gt;.
&lt;img src=&quot;/images/2019/20190710/TrustMicrophone.jpg&quot; alt=&quot;Trust Microphone&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This is the extra microphone that I needed! I even recorded with this one in a hotel, because of it’s size, it can easily be taken with me on-the-go.&lt;/p&gt;

&lt;h2 id=&quot;recording&quot;&gt;Recording&lt;/h2&gt;
&lt;p&gt;After using PowerPoint I found that I needed a way to suppress the fans of my laptop: they do start to make a sound that you can hear in the first video’s: as soon as I start recording, everything goes over the video card and the fans start spinning 🙁. To overcome this I now use &lt;a href=&quot;https://streamlabs.com/streamlabs-obs&quot;&gt;Streamlabs OBS&lt;/a&gt;. This open source streaming tool can also record and has the key-feature that I needed: noise filtering! Getting the level of the filter setup is still a search, so in some of the video’s the sound is rather soft. I trust that this will improve with more experience.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Note: Streamlabs OBS is an adaptation of another open source tool: [OBS](https://obsproject.com/).
OBS itself doesn&apos;t have great HiDPI support that I really needed on my 4K screen. It&apos;s not doable without it. Streamlabs has better support for it and the navigation is a lot easier to use.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;playlist&quot;&gt;Playlist:&lt;/h2&gt;

&lt;p&gt;You can find the entire playlist &lt;a href=&quot;https://www.youtube.com/watch?v=eEB9h8mU8rY&amp;amp;list=PLXVVwOM8uv2wQyhQ7mB_Nv_iXyMuXf-GT&quot;&gt;here&lt;/a&gt;.
Some of the topics explained are:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;What is DevOps?&lt;/li&gt;
  &lt;li&gt;If it hurts, do it more often&lt;/li&gt;
  &lt;li&gt;T-Shaped engineers&lt;/li&gt;
  &lt;li&gt;Shift Left&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I try to keep them as short as possible.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>GDBC: Link overview</title>
			<link href="https://devopsjournal.io/blog/2019/07/07/GDBC-link-overview"/>
			<updated>2019-07-07T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2019/07/07/GDBC-link-overview</id>
			<content type="html">&lt;p&gt;Last month we got the opportunity to organize the Global DevOps Bootcamp (&lt;a href=&quot;https://www.globaldevopsbootcamp.com&quot;&gt;link&lt;/a&gt;) and it was a blast!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190618/2019-06-18_01_GDBC_Logo.png&quot; alt=&quot;GDBC Logo&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I wanted to create an overview of all blogposts that I could find about the event, so here it is.&lt;/p&gt;

&lt;h1 id=&quot;links&quot;&gt;Links&lt;/h1&gt;

&lt;h2 id=&quot;pre-event-registration&quot;&gt;Pre-event registration&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;&quot;&gt;Jasper Gilhuis&lt;/a&gt; wrote down how he handled the pre-event registration of venues and enable them to register the attendees. Read about it &lt;a href=&quot;https://jaspergilhuis.nl/2019/06/18/global-devops-bootcamp-write-up-registration-process/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;azure-learnings&quot;&gt;Azure Learnings&lt;/h2&gt;
&lt;p&gt;A post by myself about all the stuff I learned while creating the automation to roll out all the resources we needed in Azure:
&lt;a href=&quot;/blog/2019/06/23/GDBC-Azure-learnings&quot;&gt;link&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;monitoring-the-event&quot;&gt;Monitoring the event&lt;/h2&gt;
&lt;p&gt;Posted by &lt;a href=&quot;https://twitter.com/mivano&quot;&gt;Michiel van Oudheusden&lt;/a&gt; on the things he did for all the &lt;a href=&quot;https://mindbyte.nl/2019/06/28/how-to-monitor-a-24-hour-global-event.html&quot;&gt;monitoring&lt;/a&gt; of the infrastructure for the event.&lt;/p&gt;

&lt;h2 id=&quot;48-hours-running-the-global-event&quot;&gt;48 hours running the global event&lt;/h2&gt;
&lt;p&gt;A post by myself about the day leading up to the event and during the day itself. We ran all the infrastructure we needed as a team. You can read it &lt;a href=&quot;/blog/2019/06/18/GDBC-48-hours-in-the-life-of-a-team-member&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;application-insights&quot;&gt;Application Insights&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://twitter.com/mivano&quot;&gt;Michiel van Oudheusden&lt;/a&gt; shares how he enabled Application Insights to track dependecies in our infrastructure &lt;a href=&quot;https://mindbyte.nl/2019/06/28/use-application-insights-over-multiple-systems-to-track-dependencies.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;challenges&quot;&gt;Challenges&lt;/h2&gt;
&lt;p&gt;The GDBC team has opened up the challenges website we used during the event itself so everyone can go through the challenges (and behind the scenes videos) to keep on learning! Available under the creative commons (non-commercial) license on &lt;a href=&quot;https://www.gdbc-challenges.com/&quot;&gt;gdbc-challenges.com&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;looking-back-at-the-event-day&quot;&gt;Looking back at the event day&lt;/h2&gt;
&lt;p&gt;There are a lot of people who blogged about their day at the event, how they found out about it at all and those posts are awesome to read.&lt;/p&gt;

&lt;p&gt;Here is an overview:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190707/20190707_01_DonovanBrown.jpeg&quot; alt=&quot;Donovan Brown in Sweden for GDBC&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://twitter.com/thomas_ruemmler&quot;&gt;Thomas Rümmler&lt;/a&gt; helped organize an event in Stuttgart, Germany and wrote about the experience &lt;a href=&quot;https://www.aitgmbh.de/blog/tfs-devops/rueckblick-global-devops-bootcamp-2019/&quot;&gt;here&lt;/a&gt;.
&lt;!-- markdown-link-check-disable --&gt;&lt;/li&gt;
  &lt;li&gt;In Stockholm Sweden &lt;a href=&quot;https://www.linkedin.com/in/antariksh-mistry-50937a129&quot;&gt;Antariksh Mistry&lt;/a&gt; was at a venue organized by &lt;a href=&quot;https://solidify.se/&quot;&gt;Soldify&lt;/a&gt;.&lt;!-- markdown-link-check-enable --&gt; This was his first GDBC and you can read about his experience &lt;a href=&quot;https://azurebiztalkread.wordpress.com/2019/06/22/global-devops-bootcamp-2019-stockholm/&quot;&gt;here&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Some venues even created a video of their day! &lt;a href=&quot;https://vimeo.com/343240341&quot;&gt;This one&lt;/a&gt; is from a venue in Bogotá, Colombia and &lt;a href=&quot;https://youtu.be/R8-Od8O4BC4&quot;&gt;this one&lt;/a&gt; is from Vancouver, Canada. This one is from &lt;a href=&quot;https://www.youtube.com/watch?v=3vat8qrWqu0&amp;amp;feature=youtu.be&quot;&gt;Quebec, Canada&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://twitter.com/@larionov_pro&quot;&gt;Dmitry Larionov&lt;/a&gt; walked into a venue without really knowing anything about GDBC! Find out &lt;a href=&quot;https://blog.larionov.pro/2019/06/18/fun-of-being-at-global-devops-bootcamp/&quot;&gt;here&lt;/a&gt; what he thought of it :smile:.&lt;/li&gt;
  &lt;li&gt;In Vancouver, Canada &lt;a href=&quot;https://twitter.com/wpschaub&quot;&gt;Willy-Peter&lt;/a&gt; wrote down their &lt;a href=&quot;https://wikipedia.org/wiki/Link_rot&quot;&gt;feedback&lt;/a&gt;.
&lt;!-- markdown-link-check-disable --&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.linkedin.com/in/hannupekka-sormunen/&quot;&gt;Hannupekka Sormunen&lt;/a&gt; visited a venue in Helsinki, Finland and posted &lt;!-- markdown-link-check-enable --&gt;his whole dairy &lt;a href=&quot;https://sorhanp.github.io/programming/2019/06/18/Global-DevOps-Bootcamp.html&quot;&gt;here&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;In Zaragoza (Spain), &lt;a href=&quot;https://dev.to/vronik&quot;&gt;Veronica Rivas&lt;/a&gt; helped to organize their second GDBC an wrote about it &lt;a href=&quot;https://dev.to/dotnetters/global-devops-bootcamp-zaragoza-2019-by-dotnetters-3ah2&quot;&gt;here&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Read &lt;a href=&quot;https://wikipedia.org/wiki/Link_rot&quot;&gt;here&lt;/a&gt; about the event in Sofia, Bulgaria, which they organized for the third time (perfect score!).&lt;/li&gt;
  &lt;li&gt;The organizers in Toronto, Canada blogged &lt;a href=&quot;https://objectsharp.com/blog/global-devops-bootcamp-toronto-2019-thats-a-wrap&quot;&gt;here&lt;/a&gt; about the event.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://twitter.com/DavidRGardiner&quot;&gt;David Gardiner&lt;/a&gt; helped organizing the third edition in Adelaide, Australia. Read about their day &lt;a href=&quot;https://david.gardiner.net.au/2019/06/global-devops-bootcamp.html&quot;&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;behind-the-scenes&quot;&gt;Behind the scenes&lt;/h2&gt;
&lt;p&gt;If you want to see what the team created to run the event, you can find all the behind the scenes video’s here: &lt;a href=&quot;https://xpir.it/GDBC2019&quot;&gt;https://xpir.it/GDBC2019&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;keynotes&quot;&gt;Keynotes&lt;/h2&gt;
&lt;p&gt;Here are some of the keynotes that have been published online.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Year&lt;/th&gt;
      &lt;th&gt;Speaker&lt;/th&gt;
      &lt;th&gt;Topic&lt;/th&gt;
      &lt;th&gt;Link&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;2017&lt;/td&gt;
      &lt;td&gt;Donovan Brown&lt;/td&gt;
      &lt;td&gt;What is DevOps&lt;/td&gt;
      &lt;td&gt;No video online&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;2018&lt;/td&gt;
      &lt;td&gt;Buck Hodges&lt;/td&gt;
      &lt;td&gt;How Microsoft does DevOps when creating Azure DevOps&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=aIiLhK0NIlY&quot;&gt;YouTube&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;2019&lt;/td&gt;
      &lt;td&gt;Niall Murphy&lt;/td&gt;
      &lt;td&gt;Azure SRE Practices&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://youtu.be/ayyWqGlqjCc&quot;&gt;YouTube&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
</content>
		</entry>
	
		<entry>
			<title>GDBC: Azure learnings from running at scale</title>
			<link href="https://devopsjournal.io/blog/2019/06/23/GDBC-Azure-learnings"/>
			<updated>2019-06-23T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2019/06/23/GDBC-Azure-learnings</id>
			<content type="html">&lt;p&gt;On the 15th of June we got the opportunity to organize the Global DevOps Bootcamp edition of 2019 (see &lt;a href=&quot;https://www.globaldevopsbootcamp.com&quot;&gt;link&lt;/a&gt;) and we had a blast!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190618/2019-06-18_01_GDBC_Logo.png&quot; alt=&quot;GDBC Logo&quot; /&gt;&lt;/p&gt;

&lt;p&gt;For the 2018 edition we created challenges for the attendees to setup their CI/CD pipelines to push a web application into Azure. You can read up on the setup for that edition &lt;a href=&quot;/blog/2018/09/02/GDBC-DevOps-Pipelines&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;next-level&quot;&gt;Next level&lt;/h2&gt;
&lt;p&gt;Since we need to create something next level for each new edition we had a brainstorm session somewhere in November of 2018. How can we improve the experience for 2019?? We got quite some feedback that for the attendees that had no previous experience with Azure that some steps (e.g. creating a Service Principal) where to hard for them, both to understand and to execute. Although that story has improved a lot with for example the release of the &lt;a href=&quot;https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest&amp;amp;WT.mc_id=AZ-MVP-5003719&quot;&gt;Azure CLI&lt;/a&gt;, we wanted to make it even easier: what if we setup &lt;strong&gt;everything&lt;/strong&gt; for the teams?&lt;/p&gt;

&lt;h2 id=&quot;team-setup&quot;&gt;Team setup&lt;/h2&gt;
&lt;p&gt;So we would start them with a working web application, running in Azure including a database connection and application insights to boot. We also wanted to set them up with a complete &lt;a href=&quot;https://dev.azure.com&quot;&gt;Azure DevOps&lt;/a&gt; setup, so everything from a Git repository, CI/CD pipelines, working service connections, package management, the whole deal.&lt;/p&gt;

&lt;h2 id=&quot;find-out-more&quot;&gt;Find out more&lt;/h2&gt;
&lt;p&gt;If you want to know more about the whole setup, checkout the YouTube playlist the team has made around the event &lt;a href=&quot;https://www.youtube.com/watch?list=PLCnpc4jNC9lBPR65GtrXYMXyge4VKll9l&amp;amp;v=VPKNvE9Lnpk&quot;&gt;here&lt;/a&gt;. I helped creating the Azure DevOps scripts (together with &lt;a href=&quot;https://xpirit.com/rene&quot;&gt;René&lt;/a&gt;, &lt;a href=&quot;https://xpirit.com/jasper&quot;&gt;Jasper&lt;/a&gt; and &lt;a href=&quot;https://xpirit.com/sofie&quot;&gt;Sofie&lt;/a&gt;) and created the Azure setup myself. You can also look at this video explaining the Azure DevOps and Azure parts:&lt;/p&gt;
&lt;iframe width=&quot;900&quot; height=&quot;506&quot; src=&quot;https://www.youtube.com/embed/VPKNvE9Lnpk?list=PLCnpc4jNC9lBPR65GtrXYMXyge4VKll9l&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;If you really want to find out more, I would love to come talk about all of it on conferences or meetups! You can find my session &lt;a href=&quot;https://sessionize.com/s/RobBos/running_a_1200_team_global_devops_b/24926&quot;&gt;here&lt;/a&gt;. I am sending this session in for conferences, either alone or together with &lt;a href=&quot;https://sessionize.com/s/ReneVanOsnabrugge/running_a_1200_team_global_devops_b/24718&quot;&gt;René&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id=&quot;size&quot;&gt;Size&lt;/h1&gt;
&lt;p&gt;We decided to cap the venue count this year to a 100 venues. This would give us a limit in the amount of resources we needed to create and we could calculate an indication of the costs of those resources. We got 4 sponsored Azure subscriptions from &lt;a href=&quot;https://azure.microsoft.com/?WT.mc_id=AZ-MVP-5003719&quot;&gt;Microsoft&lt;/a&gt; with limited budgets on them. From the tickets the venue organizers added in Eventbrite we could guess the scale we needed.&lt;/p&gt;

&lt;p&gt;In the end we created:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;1340 App Services,&lt;/li&gt;
  &lt;li&gt;96 SQL servers,&lt;/li&gt;
  &lt;li&gt;1340 SQL databases,&lt;/li&gt;
  &lt;li&gt;1340 Azure DevOps Teams in 7 Azure DevOps organizations (each with a full setup)&lt;/li&gt;
  &lt;li&gt;96 AAD Venue Team groups and users,&lt;/li&gt;
  &lt;li&gt;1340 AAD Team groups and users,&lt;/li&gt;
  &lt;li&gt;1340 AAD Team Service Principals&lt;/li&gt;
  &lt;li&gt;1340 team role assignments in 1340 + 96 resource groups&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;learnings&quot;&gt;Learnings&lt;/h1&gt;
&lt;p&gt;Running at a scale like this means we where going to hit some weird stuff in the API’s we used. Here is what I learned by provisioning the Azure resources. There where some small things like the fact that not all resource providers are registered in new subscriptions. I knew that this was the case, but totally forgot about it. When I hit this issue, I was surprised that this even happens for the most ‘basic’ resource providers, like Application Insights and KeyVault! My colleague Pascal Naber has a &lt;a href=&quot;https://pascalnaber.wordpress.com/2017/05/30/fixing-the-subscription-is-not-registered-to-use-namespace-microsoft-xxx/#more-621&quot;&gt;blogpost&lt;/a&gt; about how to enable all the providers for a subscription. In our case I decided to not use that, and enable the providers we needed by hand in the four subscriptions we had. The main reason behind this was that we needed to make all the venue organizers &lt;strong&gt;and&lt;/strong&gt; the team accounts &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Owner&lt;/code&gt; on the resource groups we would create for them, so they could actually do the things we wanted them to do. That would give them the rights to spin up everything they need, including the potentially more expensive things like ASE, DataBricks clusters or just VM’s. Just to be on the safe side I choose to leave those possibilities disabled.&lt;/p&gt;

&lt;h2 id=&quot;azure-learning-cloud-resources-are-not-always-finite&quot;&gt;Azure learning: Cloud resources are not always finite&lt;/h2&gt;
&lt;p&gt;I found out that Azure has limits on the amount of available resources you can create in each &lt;a href=&quot;https://azure.microsoft.com/en-us/global-infrastructure/regions/?WT.mc_id=AZ-MVP-5003719&quot;&gt;region&lt;/a&gt;. I already experienced that when I was attending Microsoft’s AI Bootcamp in January, when the proctors there indicated limited amounts of DataScience VM’s in each region. I figured this was a limitation based on the compute resources needed and the usual ‘standard’ resources would not have this limitation.&lt;/p&gt;

&lt;p&gt;Imaging my surprise when I tested with creating SQL Servers in India:
&lt;img src=&quot;/images/2019/20190623/20190623_01_SQL_Server_India.png&quot; alt=&quot;SQL Servers not available in India&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Even switching to one of the other two regions in India didn’t help! Apparently some quota is set on Azure’s backend and you cannot create those resources 😲. Even testing a couple of weeks later still indicated it wasn’t possible! Eventually we moved those teams to &lt;a href=&quot;https://azure.microsoft.com/en-us/global-infrastructure/regions/?WT.mc_id=AZ-MVP-5003719&quot;&gt;Southeast Asia&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;azure-learning-portal-search-is-not-great&quot;&gt;Azure learning: Portal search is not great&lt;/h2&gt;
&lt;p&gt;When you have a large Azure Subscription with a lot of resources in it, the search just is not that great. Searching for a wildcard in the beginning of a name isn’t possible, so searching for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%brisbane%&lt;/code&gt; was not possible. That meant a lot of copy and pasting if I needed to find the teams resources to check them for something.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190623/20190623_05_PortalSearch.png&quot; alt=&quot;Azure Portal&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;azure-learning-azure-sql-server-limits&quot;&gt;Azure learning: Azure SQL server limits&lt;/h2&gt;
&lt;p&gt;As I always want to segment users and teams from each other as much as possible, I wanted to create an Azure SQL Server per team at first. I knew there are soft limits and hard limits in Azure for some resource providers, like for example the amount of &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-supportability/resource-manager-core-quotas-request?WT.mc_id=AZ-MVP-5003719&quot;&gt;CPU cores&lt;/a&gt; you can allocate. There is a support process around it where you can ask to increase those quotas.&lt;/p&gt;

&lt;p&gt;That reminded me to check the quotas for SQL Servers, Databases and App Service Plans that we needed to create. Luckily I did: there is a soft limit of 20 Azure SQL Servers per region in a subscription that can be increased with support tickets and a &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/sql-database/sql-database-resource-limits-database-server?WT.mc_id=AZ-MVP-5003719&quot;&gt;hard limit&lt;/a&gt; of 200 Azure SQL Servers &lt;strong&gt;per subscription&lt;/strong&gt;!
That would not work for the 1200+ teams that we had planned by the time I found out about this limit! We decided to switch to provision 1 SQL Server per venue (planning on max 100) and then create the database per team. That would mean that the teams could see and control the databases of ALL the teams in their venue, but we expected the teams to not screw up the databases of other teams. I also made sure the venue organizer would get a VenueAdmin account that could help out the teams in case of need. Since I wanted the attendees to be able to monitor their database for at least the Percentage DTU consumed, it meant that they needed to have those rights on the &lt;em&gt;server&lt;/em&gt;, since you cannot set ACL’s on the database level. Note: we did create SQL Server User accounts for the databases as well, so we had different passwords per team.&lt;/p&gt;

&lt;h2 id=&quot;azure-learning-application-insights-not-available-in-all-regions&quot;&gt;Azure learning: Application Insights not available in all regions&lt;/h2&gt;
&lt;p&gt;Application Insights resource provider is not available in Central US! For me, Application Insights is a base provider that I’d use in any application I create or consult for. That made me expect that this provider would be available world wide, or at least at what I would consider &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;standard&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;big regions&lt;/code&gt;. This is not the case! So the advise is: always check! We got tipped beforehand by an observant venue organizer in the last week before the event and we moved those web applications to East US.&lt;/p&gt;

&lt;h2 id=&quot;azure-learning-2000-role-assignments-per-subscription&quot;&gt;Azure learning: 2000 role assignments per subscription&lt;/h2&gt;
&lt;p&gt;At first, I was making role assignments on a user level, based on the fact that I had that call working. There is a limit on how many assignments you can make per &lt;strong&gt;subscription&lt;/strong&gt;! Read more about it in the &lt;a href=&quot;https://docs.microsoft.com/nl-nl/azure/role-based-access-control/troubleshooting?WT.mc_id=AZ-MVP-5003719&quot;&gt;docs&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;If you get the error message &quot;No more role assignments can be created (code: RoleAssignmentLimitExceeded)&quot; when you try to assign a role, try to reduce the number of role assignments by assigning roles to groups instead. Azure supports up to 2000 role assignments per subscription.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;azure-learning-portal-only-shows-the-first-2000-resources-in-any-pane&quot;&gt;Azure learning: portal only shows the first 2000 resources in any pane&lt;/h2&gt;
&lt;p&gt;This only makes sense and is also present in the REST API, but the portal ‘only’ shows 2000 resources in the listings. This is at least the case in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;All resources&lt;/code&gt; view.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190623/20190623_04_AzurePortalLimits.png&quot; alt=&quot;Azure Portal showing only 2000 resources&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;azure-learning-always-check-the-defaults-or-pick-your-own&quot;&gt;Azure learning: always check the defaults or pick your own&lt;/h2&gt;
&lt;p&gt;This one was something I had not thought of for a long time: I was creating the Azure SQL databases with the default during testing. This meant that the teams got a S0 database which would cost us €0.0171/hour/database (remember that we needed 1350 databases!). Right in the period that we ran the full set of resources, &lt;strong&gt;the default changed&lt;/strong&gt;!! Azure now picked a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gen 5 with 4 vCores&lt;/code&gt; that costed €0.9021/hour/hour/database! I ran through € 5.000 in less than a day, because of this!! 😱😱😱😱. Lucky that I found this, I ran an updated on all of them to resize them back to an S0. Also updated the code so I picked my own default and set that to an S0 as well.
You can find out more information about pricing &lt;a href=&quot;https://azure.microsoft.com/en-us/pricing/details/sql-database/single/?WT.mc_id=AZ-MVP-5003719&quot;&gt;here&lt;/a&gt;.
I could have also used a SQL Elastic Pool per venue and place the database in it, that would just have costed less euro’s and more time to build. I skipped it because I needed to do a lot of things in that last week 😉. You can read more about the last 48 hours of the event in &lt;a href=&quot;/blog/2019/06/18/GDBC-48-hours-in-the-life-of-a-team-member&quot;&gt;another post&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;azure-learning&quot;&gt;Azure learning:&lt;/h2&gt;
&lt;p&gt;There are some calls that you can make that can fail in a weird way. I had several calls fail where I created both the Active Directory Application and the Service Principal to go along with it. Given that I made 1350 calls to this API and &amp;gt; 20 of them failed, this is quite interesting. Anyway: on failure: the Application would be created and the Service Principal &lt;strong&gt;would not&lt;/strong&gt;! I quickly added a check on it that would just delete the Application so the next run would recreate it.
Not sure if this is caused by the &lt;a href=&quot;https://github.com/Azure/azure-libraries-for-net&quot;&gt;Fluent SDK&lt;/a&gt; that I was using.&lt;/p&gt;

&lt;div class=&quot;language-c# highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;servicePrincipal&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AzureClientsFactory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AADManagementClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ServicePrincipals&lt;/span&gt;
                        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Define&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;spnName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WithNewApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;spnName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;DefinePasswordCredential&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ServicePrincipalPassword&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WithPasswordValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;spnPassword&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Attach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
                        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ConfigureAwait&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;azure-devops-learnings&quot;&gt;Azure DevOps learnings&lt;/h1&gt;
&lt;p&gt;I encountered the Azure DevOps REST API’s again and as in previous times, I am &lt;strong&gt;very&lt;/strong&gt; grateful for all the experience and knowledge my colleagues &lt;a href=&quot;https://xpirit.com/rene&quot;&gt;René&lt;/a&gt;, &lt;a href=&quot;https://xpirit.com/jasper&quot;&gt;Jasper&lt;/a&gt; and &lt;a href=&quot;https://xpirit.com/jesse&quot;&gt;Jesse&lt;/a&gt; have with them! These API’s are &lt;a href=&quot;https://docs.microsoft.com/en-us/rest/api/azure/devops/?view=azure-devops-rest-5.0&quot;&gt;documented&lt;/a&gt; but we seem to always need some exotic variant or a specific thing that is hard to find, for example setting default repository permissions on your Azure DevOps Organization, as documented by Jesse &lt;a href=&quot;https://jessehouwing.net/azure-devops-git-setting-default-repository-permissions/&quot;&gt;here&lt;/a&gt;.
We knew that we needed to setup 1200+ team projects and the GDBC team has excellent contacts with the Azure DevOps product group, so we arranged 7 different Azure DevOps organizations. This would enable us to:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;spread the load on the service, we hit some weird quota’s and functionality issues last year,&lt;/li&gt;
  &lt;li&gt;keep the latency low for the teams, since we could place them to an organization closest to them,&lt;/li&gt;
  &lt;li&gt;run the CI/CD pipelines that would create the App Services and other resources as close as possible to the start of the event, so we could keep the costs low.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;dossing-the-azure-devops-service&quot;&gt;DOSsing the Azure DevOps service&lt;/h2&gt;
&lt;p&gt;Since we had 7 different organizations, &lt;a href=&quot;https://xpirit.com/rene&quot;&gt;René&lt;/a&gt; created a nice pipeline to start all the CI/CD pipelines after we created all the other setup (Git repo’s, Service connections, etc.).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190623/20190619_07_MassivePipeline.png&quot; alt=&quot;Azure DevOps Full pipeline&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Getting the sponsoring from Microsoft also meant an increase in available &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=ms.build-release-hosted-pipelines&quot;&gt;Microsoft Hosted Pipelines&lt;/a&gt;. I learned later that you cannot even buy these numbers of pipelines as a regular customer! This meant that we either had 100 0r 160 hosted pipelines available (depending on the estimated size of the organization we would set up), so we could kick off all +/- 400 pipelines in each organization when needed.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190623/20190623_02_AzureDevOps_Pipelines.png&quot; alt=&quot;Azure DevOps concurrent pipelines&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This would mean scheduling &lt;strong&gt;all 400 CI builds&lt;/strong&gt; when we wanted, witch would all kick off their CD release on completion. This eventually meant rapid scaling of the pipelines in the region the Azure DevOps organization was linked to. Two off these regions had some serious issues handling that load!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190623/20190623_03_AzureDevOpsOutage.png&quot; alt=&quot;Azure DevOps outage in Brazil and Australia&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Eventually this was all sorted out by SRE’s in the Azure DevOps team, with great, direct support for us. I think the learned something about their own service in the end!&lt;/p&gt;

&lt;h2 id=&quot;azure-devops-functionality-view-in-progress-jobs-bug&quot;&gt;Azure DevOps functionality View in-progress jobs (bug)&lt;/h2&gt;
&lt;p&gt;Regarding the fly-out of the ‘View in-progress jobs’: there is a call to the backend to load all the running jobs. The fly-out will only show &lt;em&gt;after&lt;/em&gt; that callback has completed. With a lot of team projects, this can take a long time to actually do the flying out. During that wait time, you can click the link multiple times: you will then get the fly-out multiple times and you can then close them one by one :grin:.&lt;/p&gt;

&lt;p&gt;A refresh button on the fly-out would be nice as well, since this was one of the places I could check to see how the load on the organization was going and if we could start the next stage of our pipeline: right now you need to close the fly-out and then request it again :smirk:.&lt;/p&gt;

&lt;p&gt;Another small issue I have with this fly-out is that in more and more places, this fly-out can be closed by clicking on the area outside of it (for example in the build progress view). This fly-out hasn’t gotten this treatment yet.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Before you know, it is in production</title>
			<link href="https://devopsjournal.io/blog/2019/06/19/Before-you-know-it-is-in-production"/>
			<updated>2019-06-19T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2019/06/19/Before-you-know-it-is-in-production</id>
			<content type="html">&lt;p&gt;When I am working on something, usually software, I know from experience that a simple tool to test something out (e.g. a POC, Proof of Concept), can be in production in no-time.
That is when I start to focus on everything we start to ignore:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;don’t write unit tests, it is only a POC;&lt;/li&gt;
  &lt;li&gt;we don’t need to make this resilient, it is only to proof this will work;&lt;/li&gt;
  &lt;li&gt;just name the project yyyyMMddd.TestProject.exe, we will never need to find it again, if this works;&lt;/li&gt;
  &lt;li&gt;we don’t need to make this scalable yet, we’ll figure that out in the future.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I am just as guilty of this, hence this post for later referral :smile::&lt;/p&gt;

&lt;h2 id=&quot;guilty-as-charged&quot;&gt;Guilty as charged!&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2019/20190619/20190619_08_TheCauseOfThis.png&quot; alt=&quot;The Cause of this&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;case-in-point&quot;&gt;Case in point&lt;/h1&gt;
&lt;p&gt;Recently I was a member of the core team for running the infrastructure for &lt;a href=&quot;https://www.globaldevopsbootcamp.com&quot;&gt;Global DevOps Bootcamp&lt;/a&gt;.
&lt;img src=&quot;/images/2019/20190618/2019-06-18_01_GDBC_Logo.png&quot; alt=&quot;GDBC Logo&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When we started, Jasper Gilhuis was trying to automate everything we needed to create the accounts and pages for the venues in Eventbrite (read more about his experience &lt;a href=&quot;https://jaspergilhuis.nl/2019/06/18/global-devops-bootcamp-write-up-registration-process/&quot;&gt;here&lt;/a&gt;) and he could not find an API for making the venue organizers a Co-admin in our Eventbrite event.&lt;/p&gt;

&lt;p&gt;We needed this because we wanted to have the venue organizers have visibility into their registered attendees and be able to send them in-mails from the platform. First we created the global event and added the local venues as a sub-event. Then we wanted to add their accounts as a co-admin. Do check out Jasper’s post for the why’s behind this setup.&lt;/p&gt;

&lt;h1 id=&quot;every-tool-is-a-hammer&quot;&gt;Every tool is a hammer&lt;/h1&gt;
&lt;p&gt;I believe that every tool is a hammer: even if you know you should do this, you know you can do it with your tool: before you know it, you are hammering in a nail with a phone (check YouTube, it happens!).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190619/20190619_01_Every_tool_is_a_hammer.jpg&quot; alt=&quot;Image of Adam Savage&apos;s book: every tool is a hammer&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;selenium-is-on-of-my-hammers&quot;&gt;Selenium is on of my hammers&lt;/h2&gt;
&lt;p&gt;Seeing that the Eventbrite API was not able to do what we wanted, and that the flow in the website didn’t seem that hard, I made a new console application for this to use Selenium to click through the website. My first contribution to GDBC this year! As this was a tool to help with the Eventbrite automation, this ended up in the Eventbrite repository.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190619/20190619_02_FirstCommit.png&quot; alt=&quot;First commit in GDBC repository&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;we-need-to-store-some-state&quot;&gt;We need to store some state!&lt;/h1&gt;
&lt;p&gt;After a while we needed to store the state of the venue registrations: which venues where already mailed and checking into our Slack channel, etc.? We tried to do this with a Table in an Azure Storage Account, but found out the hard way that you need to update the full document, otherwise you will only see the columns you just updated: the rest &lt;em&gt;will be gone&lt;/em&gt;. So, us having a lot of experience with SQL server used the tool we knew that would work Azure SQL (another hammer!). Switching to that immediately triggered me to start with Entity Framework to make the communication as easy for me as could be (hammertime!).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190619/20190619_03_DBContext.png&quot; alt=&quot;GDBC Db Context project added&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;we-need-to-show-the-venues-on-the-website&quot;&gt;We need to show the venues on the website!&lt;/h1&gt;
&lt;p&gt;A team member asked to use the data we had to update the website and show the locations, registration urls and the number of venues on our main (&lt;a href=&quot;https://www.globaldevopsbootcamp.com&quot;&gt;website&lt;/a&gt;). Since this was already in the ConsoleApp, I added this as an extra startup action: Run the application like this and it will update a blobstorage container with the latest version of a data file that the website could then use to show the data 👇.&lt;/p&gt;

&lt;div class=&quot;language-c# highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;ConsoleApp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exe&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Since I did not want to run this application by hand but every night, I found … another tool to do so!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190619/20190619_04_VenueUpdatePipeline.png&quot; alt=&quot;Azure DevOps Venue Update Pipeline&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;provisioning-azure-resources&quot;&gt;Provisioning Azure Resources&lt;/h1&gt;

&lt;p&gt;After a couple of weeks we needed to start rolling out Azure Infrastructure (for more information, check out this &lt;a href=&quot;https://www.youtube.com/watch?v=VPKNvE9Lnpk&amp;amp;list=PLCnpc4jNC9lBPR65GtrXYMXyge4VKll9l&amp;amp;index=7&amp;amp;t=2s&quot;&gt;YouTube video&lt;/a&gt; where I explain what we did. All the resources we needed to create had a link back to the venue and teams that where in the database…. And so I added just another startup action to the tool to start running the steps to provision everything in Azure, based on the information in the database. Can you see where this is going???&lt;/p&gt;

&lt;h1 id=&quot;startup-actions-for-when-you-want-to-run-the-tool-locally&quot;&gt;Startup actions for when you want to run the tool locally&lt;/h1&gt;
&lt;p&gt;Initially this started out to be a tool so me or Jasper could run it locally, see all the Eventbrite screens fly by (later headless) and then do some other actions where added.&lt;/p&gt;

&lt;p&gt;Today, this looks like this (what a mess!):
&lt;img src=&quot;/images/2019/20190619/20190619_05_Actions.png&quot; alt=&quot;Startup actions of the executable&quot; /&gt;
I wanted to have multiple options for the user to choose from and be able to run action 2 first and if that was executed correctly, run action 3 next.&lt;/p&gt;

&lt;h1 id=&quot;running-the-action-in-a-pipeline&quot;&gt;Running the action in a pipeline&lt;/h1&gt;
&lt;p&gt;After I added more and more actions for the tool to do, I needed to make it run inside of a pipeline, read some parameters that where passed in and then close the application. For this I always fall back to the &lt;a href=&quot;https://github.com/xamarin/XamarinComponents/tree/master/XPlat/Mono.Options&quot;&gt;Mono.Options NuGet package&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is way those settings look like today:
&lt;img src=&quot;/images/2019/20190619/20190619_06_Actions.png&quot; alt=&quot;Mono actions of the executable&quot; /&gt;&lt;/p&gt;

&lt;p&gt;So is this still the same application!? Unfortunately, it is… 😲. Every decision in this application was a combination of:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;YAGNI (You ain’t going to need it),&lt;/li&gt;
  &lt;li&gt;This is just to run once,&lt;/li&gt;
  &lt;li&gt;Lets make this work quickly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the end, it got the job done, as it is included in every stage of our main provisioning pipeline:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190619/20190619_07_MassivePipeline.png&quot; alt=&quot;Massive Azure Pipeline&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;

&lt;ol&gt;
  &lt;li&gt;Even if it is just a tool to test: write the code and name things like it will end up in production.&lt;/li&gt;
  &lt;li&gt;Every tool is a hammer and will be used as a hammer a some point.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190619/20190619_02_AdamSavageOneDayBuild.jpg&quot; alt=&quot;Adam Savage One day build&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;check-out-the-video-from-this-one-day-build-on-youtube&quot;&gt;Check out the video from this one day build on &lt;a href=&quot;https://www.youtube.com/watch?time_continue=3&amp;amp;v=G7MDrUG4cws&quot;&gt;YouTube&lt;/a&gt;&lt;/h3&gt;
</content>
		</entry>
	
		<entry>
			<title>GDBC: 48 hours in the life of a team member</title>
			<link href="https://devopsjournal.io/blog/2019/06/18/GDBC-48-hours-in-the-life-of-a-team-member"/>
			<updated>2019-06-18T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2019/06/18/GDBC-48-hours-in-the-life-of-a-team-member</id>
			<content type="html">&lt;p&gt;Last weekend we got the opportunity to organize the Global DevOps Bootcamp (&lt;a href=&quot;https://www.globaldevopsbootcamp.com&quot;&gt;link&lt;/a&gt;) and it was a blast!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190618/2019-06-18_01_GDBC_Logo.png&quot; alt=&quot;GDBC Logo&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Thanks to &lt;a href=&quot;https://twitter.com/renevo&quot;&gt;René van Osnabrugge&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/marcelv&quot;&gt;Marcel de Vries&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/molausson&quot;&gt;Mathias Olausson&lt;/a&gt; for coming up with the idea to create GDBC and sticking with the team to get this idea of the ground!
Without them and our sponsors (&lt;a href=&quot;https://www.xpirit.com&quot;&gt;Xpirit&lt;/a&gt;, &lt;a href=&quot;https://solidify.se/&quot;&gt;Solidify&lt;/a&gt;, &lt;a href=&quot;https://www.microsoft.com&quot;&gt;Microsoft&lt;/a&gt;) we could not have started with the event!&lt;/p&gt;

&lt;h1 id=&quot;team-work&quot;&gt;Team work!&lt;/h1&gt;
&lt;p&gt;To set everything up we send out a call to everyone who helped last year and also to their friends. In the end we had a team with around 15 members, each picking up tasks they could handle (or try something new!). Without all that countless effort of them we would not have been able to pull this off!&lt;/p&gt;

&lt;h1 id=&quot;the-week-leading-up-to-gdbc&quot;&gt;The week leading up to GDBC&lt;/h1&gt;
&lt;p&gt;During the last few months, we had a Monday call at 9:00 PM with the entire core team and of course René, Marcel and Mathias. In it, we discussed the progress, challenges we where facing and asking for help if needed. On and off there where more people involved (special mentions for &lt;a href=&quot;https://xpirit.com/xpiriter/sofie-wisse/&quot;&gt;Sofie&lt;/a&gt; and &lt;a href=&quot;https://xpirit.com/xpiriter/niels-nijveldt&quot;&gt;Niels&lt;/a&gt;, who supported us heavily in the last week!!!). Usually we spend half an hour to an hour keeping each other up to date.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190618/2019-06-18_02_CoreTeam.png&quot; alt=&quot;GDBC Core Team&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We all do have day jobs to feed our families, so we actually worked as much as we could during the regular working days and then switched to GDBC when we could. For most of us that meant as soon as the kids or our partners where asleep. I’ve seen a lot of input, commits and feedback after midnight, so everyone was fully committed to this cause.&lt;/p&gt;

&lt;p&gt;We lived the event through &lt;a href=&quot;https://www.slack.com&quot;&gt;Slack&lt;/a&gt;, where the whole team would communicate with everyone involved: from sponsors, core team to the local venue organizers.&lt;/p&gt;

&lt;h1 id=&quot;wednesday-and-thursday&quot;&gt;Wednesday and Thursday&lt;/h1&gt;
&lt;p&gt;Although we got a sponsorship from &lt;a href=&quot;https://www.microsoft.com&quot;&gt;Microsoft&lt;/a&gt;, our funds where limited, so we started provisioning all the infrastructure we needed for the event on Wednesday. I will dive into that setup later on, but you can see my explanation here:&lt;/p&gt;
&lt;iframe width=&quot;900&quot; height=&quot;506&quot; src=&quot;https://www.youtube.com/embed/VPKNvE9Lnpk?list=PLCnpc4jNC9lBPR65GtrXYMXyge4VKll9l&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;azure-devops-pipelines&quot;&gt;Azure DevOps Pipelines:&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2019/20190618/2019-06-18_40-pipelines.png&quot; alt=&quot;Azure DevOps Pipelines&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;resources-for-azure-subscription-1&quot;&gt;Resources for Azure Subscription 1:&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2019/20190618/2019-06-18_41_Azure.png&quot; alt=&quot;Azure Resources for subscription 1&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This didn’t go as easy as we hoped, luckily we anticipated throttling, availability issues of resources in the Azure regions we needed, etcetera, so we had plenty of time to do so. I will blog about the lessons we learned here separately and update this post with a link to it. Fortunately we had four different Azure Subscriptions to use so we could spread the load!&lt;/p&gt;

&lt;p&gt;As most of our infrastructure got available, we enabled the venue organizers to pick on of the teams available for them and start testing our infrastructure end-to-end with it. We already did that with some specific demo venues, so we got some confidence that this would mostly work. We needed their feedback, specifically because there is no place to test on, then production!&lt;/p&gt;

&lt;h1 id=&quot;thursday-call&quot;&gt;Thursday call&lt;/h1&gt;
&lt;p&gt;On Thursday we had an extra call: around 24 hours before the event would start, so we could cross the t’s and dot the last i’s. There where some last minute challenges that some of us where facing, like the flaky connection that had issues with the way the Parts Unlimited website now used the connection strings and a call to &lt;!-- markdown-link-check-disable --&gt;&lt;a href=&quot;https://dev.azure.com&quot;&gt;Azure DevOps&lt;/a&gt; that wasn’t tested with 1300 team projects :smile: .&lt;!-- markdown-link-check-enable --&gt;&lt;/p&gt;

&lt;h2 id=&quot;firewall-rules-for-challenge&quot;&gt;Firewall rules for challenge&lt;/h2&gt;
&lt;p&gt;To handle the firewall rules changes I’ve spend the rest of the evening with &lt;a href=&quot;https://twitter.com/jakobehn&quot;&gt;Jakob&lt;/a&gt; testing and updating all the SQL servers that we had provisioned so that the challenge could work.
&lt;img src=&quot;/images/2019/20190618/2019-06-18_03_FlakyConnections.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;more-teams-needed&quot;&gt;More teams needed!&lt;/h2&gt;
&lt;p&gt;During the day an organizer checked their setup and asked us for more teams! They checked the participants registrations, did the calculations and where worried they would not have enough resources to place everybody in the group size they had in mind. Only other options was making the team bigger. We had been discussing that we would communicate a stop on any changes, but decided to help this organizer out. We communicated the stop on changes a couple of minutes later to prevent us from scrambling at the last minute with changes.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190618/2019-06-18_04a.png&quot; alt=&quot;Azure extra resource groups&quot; /&gt; &lt;img src=&quot;/images/2019/20190618/2019-06-18_04b.png&quot; alt=&quot;Azure DevOps extra team projects&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;a-team-is-missing&quot;&gt;A team is missing!&lt;/h2&gt;
&lt;p&gt;During the call that evening another organizer pinged us letting us know that he missed team-05 out of the 16 teams we provisioned for them. In Azure the resource group and the team users where created, but the team project was not available in Azure DevOps. As they also indicated they had sold out all the tickets, we needed to fix this. So I planned in a new run for that venue as well.&lt;/p&gt;

&lt;h2 id=&quot;if-it-hurts-do-it-more-often&quot;&gt;If it hurts, do it more often&lt;/h2&gt;
&lt;p&gt;It is really true: if it hurts, do it more often. Especially in a DevOps team, do the things that hurt more often you’ll get good at it and you have the change to automate them! I already pressed René on this last year: we needed to have pipelines setup for as much as we could, so we wouldn’t be locked to a specific developer and their laptop to kick off updates. Since we where creating a lot of stuff, these would also take up to hours of runtime before they’d be finished, so this wasn’t helpful.
This meant that I had to setup pipelines for everything I did in Azure, otherwise René would not stop bothering me this year to do so, as he should! :kissing_heart:! Him, &lt;a href=&quot;https://xpirit.com/xpiriter/jasper-gilhuis/&quot;&gt;Jasper&lt;/a&gt; and &lt;a href=&quot;https://xpirit.com/xpiriter/sofie-wisse/&quot;&gt;Sofie&lt;/a&gt; spend their time setting up a pipeline for Azure DevOps, so we had that part automated from front to back.&lt;/p&gt;

&lt;p&gt;That meant that I could run through these steps in &lt;strong&gt;15 minutes&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Update the database with teams we needed to create or update&lt;/li&gt;
  &lt;li&gt;Create everything for them in Azure&lt;/li&gt;
  &lt;li&gt;Create everything for them in Azure DevOps&lt;/li&gt;
  &lt;li&gt;Kick off their CI/CD pipelines, so the web shops would be deployed&lt;/li&gt;
  &lt;li&gt;Update their App Services to set up the correct DNS entries and SSL Certificates&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;48-hours-running-the-infrastructure-for-the-event&quot;&gt;48 hours: running the infrastructure for the event&lt;/h1&gt;
&lt;p&gt;After having a late night on Thursday I had trouble getting a sleep. The following morning I was up at 5:30 AM still rushing with excitement: This was the big day! Last year was phenomenal, hopefully this year would be as awesome as then! I checked the last results for the pipelines that where running to verify that every resource we needed was in place (all green!).&lt;/p&gt;

&lt;p&gt;My oldest son (9 years old) had a presentation to give at his school and he wanted to practice it one more time, so I spend the hours before school with him so he could do so. After that I drove to the office to check in with the team there, for the final checks. I left at 4:00 PM to be home early, spend that time with the kids until they where a sleep and went to bed at 9:15 PM tried to sleep for some hours. My alarm clock woke me at 10:45 PM: time to login and join the Call Bridge we set up for it in Microsoft Teams!&lt;/p&gt;

&lt;p&gt;During the first checks my youngest son (4 years old) woke up and I tried to get him back to sleep. He needed a hug an a stuffed animal to keep him company.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190618/2019-06-18_00_SRE.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;11-pm-christchurch-new-zealand-starts-the-event&quot;&gt;11 PM: Christchurch New Zealand starts the event!&lt;/h2&gt;
&lt;p&gt;There was a venue in Christchurch that would start the event officially and we decided to be online for them to see if everything was running as expected and offer help if needed. They had to go through the keynote presentations first, select the teams and then start logging in to the challenges website with their account we created.
The team that decided to be on stand by was me, &lt;a href=&quot;https://xpirit.com/xpiriter/rene-van-osnabrugge/&quot;&gt;René&lt;/a&gt; and &lt;a href=&quot;https://xpirit.com/xpiriter/michiel-van-oudheusden/&quot;&gt;Michiel&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;btw-the-challenges-has-been-open-sources-under-the-creative-commons---non-commercial-license-here&quot;&gt;Btw: the challenges has been open sources under the Creative Commons - Non Commercial license &lt;a href=&quot;http://www.gdbc-challenges.com/&quot;&gt;here&lt;/a&gt;.&lt;/h3&gt;

&lt;p&gt;We already pinged them to let them know we where available and asked them to keep us up to date on how the first hours went.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190618/2019-06-18_05_Christchurch.png&quot; alt=&quot;Christchurch attendees where arriving&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The keynote was finished at 12:00 AM and we watched the infrastructure with hawk eyes! And … everything worked! Crazy thing was that some organizers in Mexico and Redmond where actively testing with one of their teams, so they where on the scoreboard! Luckily &lt;a href=&quot;https://xpirit.com/xpiriter/geert-van-der-cruijsen/&quot;&gt;Geert&lt;/a&gt;, &lt;a href=&quot;https://xpirit.com/xpiriter/niels-nijveldt&quot;&gt;Niels&lt;/a&gt; and &lt;a href=&quot;https://xpirit.com/xpiriter/chris-van-sluijsveld/&quot;&gt;Chris&lt;/a&gt; left us documentation in our wiki on how to remove them.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190618/2019-06-18_06_StartEvent.png&quot; alt=&quot;Christchurch communication&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The teams in Australia where running by then and still everything look great on our end. Besides looking for issues there wasn’t really anything to do but check the social media channels and respond there as well (we got sponsoring from &lt;a href=&quot;http://twitter.com/walls_io&quot;&gt;Walls_io&lt;/a&gt; to host a tweet wall &lt;a href=&quot;https://walls.io/&quot;&gt;here&lt;/a&gt;).&lt;/p&gt;

&lt;h2 id=&quot;200-am-crashing-team-members&quot;&gt;2:00 AM Crashing team members!&lt;/h2&gt;
&lt;p&gt;At 2:00 AM both René and Michiel really needed to go to bed and sleep to be worth anything in the morning. I was still rushing with excitement and stayed online if anything came up. I managed to do that until 4:00 AM:
&lt;img src=&quot;/images/2019/20190618/2019-06-18_07_LastCall.jpg&quot; alt=&quot;Last call&quot; /&gt;&lt;/p&gt;

&lt;p&gt;So I messaged that to the venue organizers:
&lt;img src=&quot;/images/2019/20190618/2019-06-18_06_SleepyTime.png&quot; alt=&quot;Sleepy time&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Gave a last status update to the team:
&lt;img src=&quot;/images/2019/20190618/2019-06-18_06_StatusUpdate.png&quot; alt=&quot;Status update&quot; /&gt;
And went to bed, to find my youngest son laying in my side of the bed! Seems like he needed more than just a hug and a stuffed animal to hug and my wife had laid him in our bed.&lt;/p&gt;

&lt;h1 id=&quot;20190615-global-devops-bootcamp-cest&quot;&gt;2019/06/15 Global DevOps Bootcamp (CEST)&lt;/h1&gt;
&lt;p&gt;I actually managed to sleep until 6:30AM, even with the youngest kid taking up all the available space in bed (my side only :grinning:!). Quick shower and off to the office:
&lt;img src=&quot;/images/2019/20190618/2019-06-18_08_FirstCall.jpg&quot; alt=&quot;Brushing my teeth&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In Hilversum we planned to set up the Command Center / War Room for the event. Everything was planned around this because we knew from last years that West-Europe and the America’s would take up the most resources and chatter to handle. Luckily for us: &lt;a href=&quot;https://xpirit.com/xpiriter/jasper-gilhuis/&quot;&gt;Jasper&lt;/a&gt; had taken it upon himself to host our own venue there. As usual, our hospitality team has the perfect setup to host a venue like this.&lt;/p&gt;

&lt;p&gt;At the office, we have a &lt;a href=&quot;https://www.drinkripples.com/&quot;&gt;Ripple maker&lt;/a&gt; to print on your cappuccino’s and &lt;a href=&quot;https://xpirit.com/xpiriter/jesse-houwing&quot;&gt;Jesse&lt;/a&gt; needed a laptop to upload new GDBC images for it, so I helped him out with them:
&lt;img src=&quot;/images/2019/20190618/2019-06-18_09_WakingUp.jpg&quot; alt=&quot;Coffee first&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We had 10 colleagues from Xpirit in the office to help during the day with receiving guests, proctoring the teams and running the venue in Hilversum.&lt;/p&gt;

&lt;h2 id=&quot;930-am-started-the-day-with-attendees-arriving&quot;&gt;9:30 AM Started the day with attendees arriving&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2019/20190618/2019-06-18_10_Keynote.jpg&quot; alt=&quot;Arriving attendees in Hilversum&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;1000-am-keynote-started&quot;&gt;10:00 AM Keynote started&lt;/h2&gt;
&lt;p&gt;We started the keynote in Hilversum! After a welcome note from René, Marcel and Mathias it was time to learn something about DevOps and SRE from &lt;a href=&quot;https://twitter.com/niallm&quot;&gt;Niall Murphy&lt;/a&gt;, Director of Engineering for Azure Cloud Services and Site Reliability Engineering.
&lt;img src=&quot;/images/2019/20190618/2019-06-18_11_Keynote.jpg&quot; alt=&quot;Keynote started&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We later found out his reaction about speaking in front of 10.000 attendees:
&lt;img src=&quot;/images/2019/20190618/2019-06-18_15.png&quot; alt=&quot;Response Niall Murphy: Wow.&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;we-kept-monitoring-during-the-keynote&quot;&gt;We kept monitoring during the keynote:&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2019/20190618/2019-06-18_12.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h5 id=&quot;image-courtesy-by-jesse-houwing-find-more-here&quot;&gt;Image courtesy by Jesse Houwing, find more &lt;a href=&quot;https://photos.google.com/share/AF1QipPseUvWsleanD5I5-CHhIOfy5XEUdW8qVwMTPtoUhJ9VDbBvfgzch1rKIqNhiQYBg?key=SlNkclJGTWo3MEVpcmNPb2E1SURhTXVzS01IZkFB&quot;&gt;here&lt;/a&gt;&lt;/h5&gt;

&lt;h2 id=&quot;and-during-the-rest-of-the-day&quot;&gt;And during the rest of the day&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2019/20190618/2019-06-18_13.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h5 id=&quot;image-courtesy-by-jesse-houwing-find-more-here-1&quot;&gt;Image courtesy by Jesse Houwing, find more &lt;a href=&quot;https://photos.google.com/share/AF1QipPseUvWsleanD5I5-CHhIOfy5XEUdW8qVwMTPtoUhJ9VDbBvfgzch1rKIqNhiQYBg?key=SlNkclJGTWo3MEVpcmNPb2E1SURhTXVzS01IZkFB&quot;&gt;here&lt;/a&gt;&lt;/h5&gt;
&lt;p&gt;&lt;img src=&quot;/images/2019/20190618/2019-06-18_14.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h5 id=&quot;image-courtesy-by-jesse-houwing-find-more-here-2&quot;&gt;Image courtesy by Jesse Houwing, find more &lt;a href=&quot;https://photos.google.com/share/AF1QipPseUvWsleanD5I5-CHhIOfy5XEUdW8qVwMTPtoUhJ9VDbBvfgzch1rKIqNhiQYBg?key=SlNkclJGTWo3MEVpcmNPb2E1SURhTXVzS01IZkFB&quot;&gt;here&lt;/a&gt;&lt;/h5&gt;

&lt;h2 id=&quot;closing-off-in-hilversum-with-part-of-the-team&quot;&gt;Closing off in Hilversum with part of the team&lt;/h2&gt;
&lt;p&gt;At 4:00 PM we closed the venue in Hilversum and send our own attendees home.
Some of us decided to stay at the office for a while and help the rest of the teams in the America’s that where still busy with their day. We needed to stay around our laptops and decided to get some pizza delivered instead of going to a restaurant. After the pizza we all drove home to check our feeds from there.
&lt;img src=&quot;/images/2019/20190618/2019-06-18_28_PizzaTime.jpg&quot; alt=&quot;Pizza time&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;920-pm-closing-off&quot;&gt;9:20 PM Closing off&lt;/h2&gt;
&lt;p&gt;I stayed online for a while at home until I could no longer. &lt;a href=&quot;https://xpirit.com/xpiriter/niels-nijveldt&quot;&gt;Niels&lt;/a&gt; volunteered to shut down the lights and man the fort until the last venue was done. My hero! After 7 hours asleep in 48 hours I really needed to get some sleep!!!
&lt;img src=&quot;/images/2019/20190618/2019-06-18_29_LateNight.jpg&quot; alt=&quot;Closing off&quot; /&gt;&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>SonarQube setup on Azure App Service</title>
			<link href="https://devopsjournal.io/blog/2019/04/29/SonarQube-Azure-Default-Settings"/>
			<updated>2019-04-29T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2019/04/29/SonarQube-Azure-Default-Settings</id>
			<content type="html">&lt;p&gt;As noted in a &lt;a href=&quot;/blog/2018/10/20/SonarQube-setup&quot;&gt;previous post&lt;/a&gt;, you can host a &lt;a href=&quot;https://www.sonarqube.org/&quot;&gt;SonarQube&lt;/a&gt; on an Azure App Service, thanks to &lt;!-- markdown-link-check-disable --&gt; &lt;a href=&quot;https://www.linkedin.com/in/nathan-vanderby-92a19814/&quot;&gt;Nathan Vanderby&lt;/a&gt;, a Premier Field Engineer from Microsoft. &lt;!-- markdown-link-check-enable --&gt;He created an ARM template to run the SonarQube installation behind an Azure App Service with a Java host. This saves you a lot of steps mentioned above! You can find the scripts for it on &lt;a href=&quot;https://github.com/vanderby/SonarQube-AzureAppService&quot;&gt;GitHub&lt;/a&gt; or deploy it from the big &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;deploy on Azure&lt;/code&gt; button.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190429/vikram-sundaramoorthy-1351879-unsplash.jpg&quot; alt=&quot;&quot; /&gt;
&lt;a style=&quot;background-color:black;color:white;text-decoration:none;padding:4px 6px;font-family:-apple-system, BlinkMacSystemFont, &amp;quot;San Francisco&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Ubuntu, Roboto, Noto, &amp;quot;Segoe UI&amp;quot;, Arial, sans-serif;font-size:12px;font-weight:bold;line-height:1.2;display:inline-block;border-radius:3px&quot; href=&quot;https://unsplash.com/@vikram46?utm_medium=referral&amp;amp;utm_campaign=photographer-credit&amp;amp;utm_content=creditBadge&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; title=&quot;Download free do whatever you want high-resolution photos from vikram sundaramoorthy&quot;&gt;&lt;span style=&quot;display:inline-block;padding:2px 3px&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; style=&quot;height:12px;width:auto;position:relative;vertical-align:middle;top:-2px;fill:white&quot; viewBox=&quot;0 0 32 32&quot;&gt;&lt;title&gt;unsplash-logo&lt;/title&gt;&lt;path d=&quot;M10 9V0h12v9H10zm12 5h10v18H0V14h10v9h12v-9z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;&lt;span style=&quot;display:inline-block;padding:2px 3px&quot;&gt;Photo by Vikram Sundaramoorthy&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Several benefits you get from hosting SonarQube on an App Service:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;You don’t have to manage a Virtual Machine anymore.&lt;/li&gt;
  &lt;li&gt;Setting up SSL becomes easier, thanks to the Azure App Service SSL being simpler (and free if you run on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*.azurewebsites.net&lt;/code&gt;).&lt;/li&gt;
  &lt;li&gt;Using a KeyVault for your secrets is now an option, you can inject those values as environment options.&lt;/li&gt;
  &lt;li&gt;If you already have an App Service Plan, you can use that for hosting and cut down on your resources and cost (although, running on a burstable VM isn’t that expensive)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The biggest benefit is that the App Service already has Java installed! So no more downloading the JRE by hand, installing it manually and starting the SonarQube service by hand!&lt;/p&gt;

&lt;p&gt;Setting up a new SonarQube server this way is a breeze. Updating it should be easier as well and that is the another big plus: you now have the ability to run the installation on a &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/app-service/deploy-staging-slots?WT.mc_id=AZ-MVP-5003719&quot;&gt;Deployment Slot&lt;/a&gt;, let it update the database and then switch to the slot. No manual updating anymore!&lt;/p&gt;

&lt;h1 id=&quot;set-up&quot;&gt;Set up&lt;/h1&gt;
&lt;p&gt;After creating the basic SonarQube App Service from GitHub or the ARM template, you need to create a new SQL database. Do note that you need to set the database to the correct collation for it to work: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SQL_Latin1_General_CP1_CS_AS&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;creating-a-user&quot;&gt;Creating a user&lt;/h2&gt;
&lt;p&gt;There are two options to create a new user in the database. You can create one like you would do in a full MSSQL server installation, by using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt; database. Running this way on a Azure SQL Db has a couple of downsides:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;You now have a dependency on the master database. Every new connection will have to do a lookup in the master database and then route you through to the database you want to connect to. This will create a potential bottleneck from the master database, if you are running a lot of connections or a lot of databases on the server.&lt;/li&gt;
  &lt;li&gt;Moving the database to another server takes more configuration, since the user configuration is not inside the database (see option two), but in the server itself.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;creating-a-contained-user&quot;&gt;Creating a contained user&lt;/h3&gt;
&lt;p&gt;A Contained user is a user account created &lt;strong&gt;inside the database itself&lt;/strong&gt;, making it easier to move if needed.
Run this statement in a query editor connected to your database. Of course, you can make the user a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data_reader&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data_writer&lt;/code&gt; or something else.
Since I need the application using this connection also creating the tables, stored procedures, etc, I gave it DBO rights in this database.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-SQL&quot;&gt;CREATE USER [MyUser] WITH PASSWORD = &apos;Secret&apos;;
EXEC SP_ADDROLEMEMBER N&apos;db_owner&apos;, N&apos;MyUser&apos;
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&quot;creating-a-regular-mssql-user&quot;&gt;Creating a regular MSSQL user&lt;/h3&gt;
&lt;p&gt;To create a regular MSSQL user if you do not want to have it contained in the database (see above), you can create a new SQL user by running this on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Master&lt;/code&gt; Database:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-SQL&quot;&gt;CREATE LOGIN SonarQubeUI WITH password=&apos;&amp;lt;make a new secure password here&amp;gt;&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then create a login from the new user account by running this statement &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;on the new database&lt;/code&gt; (you cannot use a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;use database_name&lt;/code&gt; statement in Azure SQL database, so you need to switch to it in the UI of your query editor):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-SQL&quot;&gt;CREATE USER SonarQubeUI FROM LOGIN SonarQubeUI
&lt;/code&gt;&lt;/pre&gt;

&lt;h1 id=&quot;settings&quot;&gt;Settings&lt;/h1&gt;
&lt;p&gt;Copy these settings from the previous steps for next use:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;SQL Server address (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mysqlserver.database.windows.net&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;DatabaseName&lt;/li&gt;
  &lt;li&gt;MSSQL Username and password&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;app-service-update&quot;&gt;App Service update&lt;/h2&gt;
&lt;p&gt;In the &lt;a href=&quot;https://portal.azure.com&quot;&gt;Azure Portal&lt;/a&gt;, find your new App Service and navigate to Advanced Tools –&amp;gt; Kudu
&lt;img src=&quot;/images/2019/20190429/2019-04-29-01_AdvancedTools.png&quot; alt=&quot;Advanced tools&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Open a debug console:
&lt;img src=&quot;/images/2019/20190429/2019-04-29-02-KuduServices.png&quot; alt=&quot;Debug console&quot; /&gt;&lt;/p&gt;

&lt;p&gt;And navigate to the configuration folder of your SonarQube and open the file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sonar.properties&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-dos&quot;&gt;D:\home\site\wwwroot\sonarqube-7.7\conf&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Update these properties:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-dos&quot;&gt;sonar.jdbc.username=SonarQubeUI
sonar.jdbc.password=&amp;lt;your sql server user password&amp;gt;
sonar.jdbc.url=jdbc:sqlserver://mysqlserver.database.windows.net:1433;databaseName=SonarQubeDb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note the portnumber and the property of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;databaseName&lt;/code&gt; instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;database&lt;/code&gt; (this was changed in SonarQube &amp;gt; 5.3).
Also note that the database name your enter is CASE-SENSITIVE! SonarQube is running on Java, hence the sensitivity.&lt;/p&gt;

&lt;p&gt;After setting these properties. Stop and Start the App Service again: SonarQube only reads in this file at startup and then initializes the new database.&lt;/p&gt;

&lt;h1 id=&quot;set-an-admin-password&quot;&gt;Set an admin password!&lt;/h1&gt;
&lt;p&gt;By default, the admin login is admin/admin, so you want to change that ASAP (after setting up the database).&lt;/p&gt;

&lt;h1 id=&quot;errors&quot;&gt;Errors?&lt;/h1&gt;
&lt;p&gt;If the SonarQube server doesn’t load after the changes, you can find the errors in the SonarQube folder here&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/logs/sonar.log
/logs/web.log
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Remember to restart the App Service after changing anything in the config!&lt;/p&gt;

&lt;h1 id=&quot;azure-active-directory&quot;&gt;Azure Active Directory&lt;/h1&gt;

&lt;p&gt;Setting up authentication for the users with Azure Active Directory is very easy, thanks to the work of the &lt;a href=&quot;https://wikipedia.org/wiki/Link_rot&quot;&gt;ALM Rangers&lt;/a&gt;. Follow the setup &lt;a href=&quot;https://github.com/hkamel/sonar-auth-aad/wiki/Setup&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Do note the Server base URL in SonarQube, I missed it the first time.
By default, this is empty!&lt;/p&gt;

&lt;p&gt;Go to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Administration&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Configuration&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;General&lt;/code&gt; –&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Server base URL&lt;/code&gt; to set the url to match the url of the App Service.&lt;/p&gt;

&lt;h1 id=&quot;blocking-anonymous-users&quot;&gt;Blocking anonymous users&lt;/h1&gt;
&lt;p&gt;If you don’t want to show the state of your projects to the entire world, don’t forget to change the default setting for this. Since the free edition is available for open source projects, this setting is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;on&lt;/code&gt; by default.
You can find it under Administration:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190429/2019-05-04SecureSonarQubeServer.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Webservice Plan Scaling in Azure Machine Learning Studio</title>
			<link href="https://devopsjournal.io/blog/2019/02/25/Webservice-Plan-Scaling-in-Azure-Machine-Learning-Studio"/>
			<updated>2019-02-25T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2019/02/25/Webservice-Plan-Scaling-in-Azure-Machine-Learning-Studio</id>
			<content type="html">&lt;p&gt;I recently found that I had a web service plan running for my Machine Learning Studio (MLS) workspace in Azure. I was hosting some test webservices on it from a research session earlier on. The web service plan was not doing anything for me, but I did incur some costs running it. Since the default tier it picks during is already an S1, this can build up if you are paying the subscription yourself.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190225/hero-photo-1508962061361-bcb4d4c477f8.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h5 id=&quot;photo-by-agê-barros&quot;&gt;Photo by &lt;a href=&quot;https://unsplash.com/photos/Yx1ZkifiHto&quot;&gt;Agê Barros&lt;/a&gt;&lt;/h5&gt;

&lt;p&gt;Finding that web service plan started by looking at the resourcegroup the MLS workspace was created in.&lt;/p&gt;

&lt;p&gt;The Azure Resource Group looks like this:
&lt;img src=&quot;/images/2019/20190225/01-ResourceGroup.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You can see the plan, but when you select it you have no extra options. No insights into the cost and no option to scale.&lt;/p&gt;

&lt;p&gt;To do any of this, you need to look inside of the workspace in MLS. To find the web services you deployed, open &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Web Services&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190225/02-AzMLS.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Here, you can only see the actual deployed services from the experiments you made (I don’t have any running here). You still cannot see the web service plan!&lt;/p&gt;

&lt;h2 id=&quot;the-trick&quot;&gt;The Trick&lt;/h2&gt;

&lt;p&gt;To actually find the web service plan and act on it, you need to go to a different interface for Azure ML:
Go to: &lt;a href=&quot;https://services.azureml.net/&quot;&gt;https://services.azureml.net/&lt;/a&gt; to manage your deployed services, then go to “Plans” and there you can administer your plans.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190225/03-MLS-Plans.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Select the plan and click on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Upgrade/Downgrade plan&lt;/code&gt;:
Now, you can actually scale the plan to your needs.
&lt;img src=&quot;/images/2019/20190225/04-MLS-Scale.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If you aren’t using the service that much, there even is a Free Plan!&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Fixing Azure Function Error RunResolvePublishAssemblies</title>
			<link href="https://devopsjournal.io/blog/2019/02/21/Azure-Function-Error-RunResolvePublishAssemblies"/>
			<updated>2019-02-21T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2019/02/21/Azure-Function-Error-RunResolvePublishAssemblies</id>
			<content type="html">&lt;p&gt;I ran into an issue with a new Azure Function I created: when I tried to run it, I got an error message about a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RunResolvePublishAssemblies&lt;/code&gt; setting.&lt;/p&gt;

&lt;h1 id=&quot;the-target-runresolvepublishassemblies-does-not-exist-in-the-project&quot;&gt;The target “RunResolvePublishAssemblies” does not exist in the project&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;/images/2019/20190221/2019_02_21_01_PowerShell_Example.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Digging around the internet did not give an indication where to look. Most examples pointed to years old &lt;a href=&quot;https://github.com/Azure/azure-functions-vs-build-sdk/issues/92&quot;&gt;issues&lt;/a&gt; that indicated this message for dotnet core version 1.0. I am running a preview version of 3.0, so that could be the issue.&lt;/p&gt;

&lt;p&gt;Testing creating another Function but with Visual Studio did have the same result: the error occurs there as well.&lt;/p&gt;

&lt;h1 id=&quot;finding-the-issue&quot;&gt;Finding the issue&lt;/h1&gt;
&lt;p&gt;Eventually I grabbed another working project with Azure Functions that we where running for a couple of months and went through all its settings to figure out what the issue could be.&lt;/p&gt;

&lt;p&gt;Checked items:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;The project SDK had been set correctly to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Microsoft.NET.Sdk&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;host.json&lt;/code&gt; pointed to a correct function host version (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2.0&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Eventually I found the culprit! Because the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;global.json&lt;/code&gt; isn’t present, the dotnet core version wasn’t fixed to any version and that is why it uses a version that doesn’t work.&lt;/p&gt;

&lt;h1 id=&quot;the-fix&quot;&gt;The fix&lt;/h1&gt;
&lt;p&gt;Add a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;global.json&lt;/code&gt; file into the root of the project folder with this content, pinning the sdk version to something working for Azure Functions, and build the project again.&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;sdk&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2.1.502&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190221/2019_02_21_01_PowerShell_Working.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Setting up Azure Monitor to trigger your Azure Function</title>
			<link href="https://devopsjournal.io/blog/2019/01/21/Setting-up-Azure-Monitor-to-trigger-your-Azure-Function"/>
			<updated>2019-01-21T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2019/01/21/Setting-up-Azure-Monitor-to-trigger-your-Azure-Function</id>
			<content type="html">&lt;p&gt;I wanted to trigger an Azure Function based on changes in the Azure Subscription(s) we where monitoring. The incoming data can than be used to do interesting things with: keeping track of who does what, see new resources being deployed or old ones being deleted, etc. Back when I started working on this, there was no &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-event-grid?WT.mc_id=AZ-MVP-5003719&quot;&gt;Event Grid&lt;/a&gt; option to use in Azure Functions, so I started with linking it to &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-monitor/overview?WT.mc_id=AZ-MVP-5003719&quot;&gt;Azure Monitor&lt;/a&gt; events. I haven’t checked the current options, so I cannot compare them yet.&lt;/p&gt;

&lt;p&gt;In this blog I wanted to show how you can do this, both by using the Azure Portal and the &lt;a href=&quot;https://docs.microsoft.com/en-us/cli/azure/?view=azure-cli-latest&amp;amp;WT.mc_id=AZ-MVP-5003719&quot;&gt;Azure CLI&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;architecture&quot;&gt;Architecture&lt;/h2&gt;
&lt;p&gt;To get &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-monitor/overview?WT.mc_id=AZ-MVP-5003719&quot;&gt;Azure Monitor&lt;/a&gt; to send in the changes that we need to see, I use this architecture:
&lt;img src=&quot;/images/2019/20190121/2019_01_21_Azure_Monitor_Architecture.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;steps&quot;&gt;Steps&lt;/h2&gt;
&lt;p&gt;To configure Azure Monitor to send all activities into an EventHub and then into our Function, you’ll need to execute several steps.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Create a new event hub&lt;/li&gt;
  &lt;li&gt;Configure the Activity Monitor to send the changes into the event hub&lt;/li&gt;
  &lt;li&gt;Configure the Event Hub to send the messages to the changes function&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;use-the-azure-portal&quot;&gt;Use the Azure Portal&lt;/h1&gt;
&lt;p&gt;How to do this manually via the Azure Portal is described below.&lt;/p&gt;

&lt;h3 id=&quot;create-a-new-event-hub-to-send-the-activity-log-to&quot;&gt;Create a new event hub to send the Activity Log to:&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/images/2019/20190121/2019_01_21_Azure_Monitor_CreateEventHub.png&quot; alt=&quot;&quot; /&gt;
The default setting of 1 throughput unit is enough for this setup, as mentioned by Microsoft’s documentation.&lt;/p&gt;

&lt;h3 id=&quot;create-the-export-of-the-activity-log&quot;&gt;Create the export of the Activity Log&lt;/h3&gt;

&lt;p&gt;Go to Activity Log, hit the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;export&lt;/code&gt; button:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190121/2019_01_21_Azure_Monitor_Activity_log_Configuration.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PICK ALL REGIONS!!&lt;/strong&gt; Most activities we want to see are GLOBAL and those would be missed otherwise.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190121/2019_01_21_Azure_Monitor_LinkEventHubToActivityLogExport.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;configure-the-event-hub-to-send-the-messages-to-the-azure-function&quot;&gt;Configure the event hub to send the messages to the Azure Function&lt;/h2&gt;
&lt;p&gt;Choose the EventHub entity you picked for the Azure Monitor to export the activities to:
&lt;img src=&quot;/images/2019/20190121/2019_01_21_Azure_Monitor_EventHubChooseHub.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You can add a consumer group to it, named ‘$default` by default.&lt;/p&gt;

&lt;h3 id=&quot;get-the-access-policy&quot;&gt;Get the Access Policy&lt;/h3&gt;
&lt;p&gt;Go to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Shared Access Policies&lt;/code&gt; and create a policy. We only need to have the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Listen&lt;/code&gt; rights so we can listen to incoming events.
&lt;img src=&quot;/images/2019/20190121/2019_01_21_Azure_Monitor_EventHubAccessPolicy.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Copy either on of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Connection strings&lt;/code&gt; and configure the Azure Function host with it:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190121/2019_01_21_Azure_Monitor_AzureFunctionEventHub.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;No need to restart the function app: the platform does that for you.&lt;/p&gt;

&lt;h1 id=&quot;use-the-azure-cli&quot;&gt;Use the Azure CLI&lt;/h1&gt;
&lt;p&gt;How to do this via the Azure CLI is described below.&lt;/p&gt;

&lt;h2 id=&quot;step-1-create-an-event-hub&quot;&gt;Step 1: Create an Event Hub&lt;/h2&gt;
&lt;p&gt;First we need to check if there already is an Event Hub and if we could use it. Use these commands to list the available namespaces in the current subscription:&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;az&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;login&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# login with an account that has the correct access rights to deploy resources on subscription level&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;az&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;eventhubs&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To search for the namespaces in a specific resource group and subscription, you can also add those parameters:&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$resourceGroup&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;resourceGroupName&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$subscription&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;subscriptionId&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;az&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;eventhubs&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--resource-group&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$resourceGroup&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--subscription&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$subscription&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You’ll get back &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]&lt;/code&gt; as an empty JSON array indicating that there are no namespaces in that list.&lt;/p&gt;

&lt;p&gt;If there is an existing namespace, you can use that to create the Log Profile with. Of course, you’ll need to check how this namespace is being used and where it sends the messages!&lt;/p&gt;

&lt;h3 id=&quot;create-a-new-namespace-with-the-cli&quot;&gt;Create a new namespace with the CLI&lt;/h3&gt;
&lt;p&gt;Use this command to create a new namespace:&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;az&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;eventhubs&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--resource-group&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$resourceGroup&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--subscription&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$subscription&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;EventHubNameSpaceCLI&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will take a couple of minutes to complete and will return the newly created namespace. You can save the JSON response in an object with this command:&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$namespace&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;az&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;eventhubs&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--resource-group&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$resourceGroup&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--subscription&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$subscription&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;EventHubNameSpaceCLI&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ConvertFrom-JSON&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;create-a-new-eventhub-in-the-namespace&quot;&gt;Create a new EventHub in the namespace&lt;/h3&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$eventhub&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;az&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;eventhubs&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;eventhub&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--subscription&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$subscription&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--resource-group&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$resourceGroup&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--namespace-name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$namespace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;EventHubCLI&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ConvertFrom-Json&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;create-a-new-authorization-rule-in-the-eventhub&quot;&gt;Create a new authorization rule in the EventHub&lt;/h3&gt;
&lt;p&gt;To be able to connect to the new EventHub, we need an authorization rule. That will have an Id that will be used as a connection string.&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ruleName&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;authorization rule name&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$rule&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;az&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;eventhubs&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;eventhub&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;authorization-rule&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--resource-group&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$resourceGroup&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--namespace-name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$namespace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--eventhub-name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$eventhub&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--subscription&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$subscription&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ListenRule&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--rights&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Listen&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ConvertFrom-Json&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# The Id of the rule that you need:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$rule&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Id&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;step-2-create-an-azure-monitor-log-profile&quot;&gt;Step 2: Create an Azure Monitor Log Profile&lt;/h2&gt;

&lt;h3 id=&quot;reconnaissance&quot;&gt;Reconnaissance&lt;/h3&gt;
&lt;p&gt;First, connect the CLI and check if there already is a profile available&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;az&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;login&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# login with an account that has the correct access rights to deploy resources on subscription level&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;az&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;monitor&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log-profiles&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# list the current log-profiles&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Note: this will run against the currently selected (or default) subscription.
You could get one of two results:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;No profile set for this subscription: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;There already is a profile for this subscription:
&lt;img src=&quot;/images/2019/20190121/2019_01_21_Azure_Monitor_AzureMonitorExportProfile.png&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If there already is a profile, carefully read through the results to see if it contains everything we need.
&lt;em&gt;Note&lt;/em&gt;: there can only be one profile per subscription. If there is only one subscription with a profile and that is set to export to the correct EventHub, that is fine.
From top to bottom this is the information you see in the image above:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Profile categories: We needs all of these, so if necessary, add the missing categories.&lt;/li&gt;
  &lt;li&gt;The SubscriptionId for this profile&lt;/li&gt;
  &lt;li&gt;The locations for which this profile will export activities. We needs at least Global and the regions that your resources have been deployed to.&lt;/li&gt;
  &lt;li&gt;The ServiceBusRuleId will indicate the namespace and authorization rule the activities will be send to. The name of this setting comes from the fact that EventHubs are build on top of Azure ServiceBusses. We needs this to be set correctly to send the activities to the EventHub that the function will listen to.&lt;/li&gt;
  &lt;li&gt;The StorageAccount indicate if this profile also exports the activities to a StorageAccount. This can be used for accountability reporting for instance: it contains all the information to find out what account executed what actions within the Azure Subscription.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;existing-profile&quot;&gt;Existing profile&lt;/h2&gt;
&lt;p&gt;If there already is an export configured to a Storage Account (but no ServiceBusRuleId), you can check the locations and categories, if those are sufficient, you can update the profile to also have a ServiceBusRuleId set to send the data into the EventHub you need.&lt;/p&gt;

&lt;p&gt;If the locations or categories are &lt;strong&gt;not&lt;/strong&gt; sufficient, you need to check the Storage Account that is being used if to see if it hurts if you send more information there. This depends on the setup that is used on top of that information.&lt;/p&gt;

&lt;h3 id=&quot;cannot-update-the-existing-profile&quot;&gt;Cannot update the existing profile?&lt;/h3&gt;
&lt;p&gt;If changing this profile is an issue, you are toast. You can create a new profile on a different subscription, but all activities on the default subscription will only be sent to the profile on that subscription!&lt;/p&gt;

&lt;h2 id=&quot;no-profile&quot;&gt;No profile&lt;/h2&gt;
&lt;p&gt;If there is no profile setup, you can create a new profile with the necessary settings like this:&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;az&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;monitor&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log-profiles&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;default&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--location&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--locations&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;global&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;eastus&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;westus&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;westeu&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;northeu&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--categories&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Delete&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Write&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Action&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--enabled&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--days&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--service-bus-rule-id&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/subscriptions/&amp;lt;subscriptionId&amp;gt;/resourceGroups/&amp;lt;resourceGroupName&amp;gt;/providers/Microsoft.EventHub/namespaces/&amp;lt;nameSpaceName&amp;gt;/authorizationrules/RootManageSharedAccessKey&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Important parameters for us:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Locations: the locations you want to send the activities for: always include &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;global&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Categories: what are the activity categories you want to receive?&lt;/li&gt;
  &lt;li&gt;Service-bus-rule-id: the EventHub rule to send the activities to.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;finding-the-service-bus-rule&quot;&gt;Finding the service bus rule&lt;/h3&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;az&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;login&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# login to the account&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;az&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;account&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--subscription&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;SubscriptionId you want to use&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# switch to the correct subscription&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$resourceGroup&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;resource group name&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# name of the resourcegroup the EventHub is in&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;az&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;eventhubs&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$resourceGroup&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# list all the eventhub namespaces in the given resourcegroup&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Find the name of the namespace you want to use and use that in the next set of commands&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;bp&quot;&gt;$Event&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Hub&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;az&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;eventhubs&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$resourceGroup&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;name of the namespace&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ConvertFrom-Json&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;az&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;eventhubs&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;eventhub&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$resourceGroup&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--namespace-name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;$Event&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Hub.Name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Find the name of the event hub in the list and use that in the next set of commands:&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$NameSpace&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;az&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;eventhubs&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;eventhub&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$resourceGroup&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--namespace-name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;$Event&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Hub.Name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;name of the eventhub&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ConvertFrom-Json&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# find all authorization rules&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;az&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;eventhubs&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;eventhub&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;authorization-rule&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--resource-group&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$resourceGroup&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--namespace-name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$NameSpace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--eventhub-name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;$Event&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Hub.Name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$AuthRule&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;az&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;eventhubs&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;eventhub&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;authorization-rule&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--resource-group&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$resourceGroup&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--namespace-name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;$Event&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Hub.Name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--eventhub-name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$NameSpace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ConvertFrom-Json&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# search for the rule with the name you want&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$ruleName&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;authorization rule name&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$rule&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;az&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;eventhubs&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;eventhub&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;authorization-rule&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--resource-group&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$resourceGroup&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--namespace-name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$NameSpace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--eventhub-name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;$Event&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Hub.Name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$ruleName&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ConvertFrom-Json&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;linking-the-eventhub-connection-to-the-azure-function&quot;&gt;Linking the EventHub connection to the Azure Function&lt;/h2&gt;
&lt;p&gt;The Azure Function needs to be told about the connection to the EventHub that it needs to listen on. For that, you need the Id of the authorization rule that we created in the previous section.&lt;/p&gt;

&lt;p&gt;You can retrieve that Id from the rule we saved:&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# The Id of the rule that you need:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$rule&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Id&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And then you can set that into the corresponding app setting for it:&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;az&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;functionapp&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;appsettings&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--resource-group&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$resourcegroup&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$functionapp&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--settings&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;EventHubConnectionAppSetting&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$rule&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Id&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The name of the setting we are changing here (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EventHubConnectionAppSetting&lt;/code&gt;) needs to match the name of the Connection you gave the parameter on the function:&lt;/p&gt;

&lt;div class=&quot;language-c# highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;FunctionName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;EventHubActivitiesFunction&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))]&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;EventHubTrigger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;insights-operational-Logs&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Connection&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;EventHubConnectionAppSetting&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;EventData&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;eventHubMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;ILogger&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For more documentation about the Event Hub binding, check &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-event-hubs?WT.mc_id=AZ-MVP-5003719&quot;&gt;docs.microsoft.com&lt;/a&gt;.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Docker for Windows: fix unauthorized errors</title>
			<link href="https://devopsjournal.io/blog/2019/01/16/Docker-for-Windows-fix-unauthorized-errors"/>
			<updated>2019-01-16T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2019/01/16/Docker-for-Windows-fix-unauthorized-errors</id>
			<content type="html">&lt;p&gt;After installing &lt;a href=&quot;https://docs.docker.com/docker-for-windows/&quot;&gt;Docker for Windows&lt;/a&gt; (recently renamed to Docker Desktop) I could not get the basic command &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker run hello-world&lt;/code&gt; working. I checked my install, read more docs, got confused if it was in my Hyper-V setup, the networking stack in it, or something else. Finally a light bulb went off and I found the solution!&lt;/p&gt;

&lt;h1 id=&quot;the-issue&quot;&gt;The issue&lt;/h1&gt;

&lt;p&gt;After installation Docker present you with a login screen.
Since that login worked just fine, I’d expect that everything is setup correctly.&lt;/p&gt;

&lt;p&gt;The next step is then to verify that you can run at least &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hello-world&lt;/code&gt;:
&lt;!-- markdown-link-check-disable --&gt;&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;C:\Users\RobBos&amp;gt; docker run hello-world
Unable to find image &apos;hello-world:latest&apos; locally
C:\Program Files\Docker\Docker\Resources\bin\docker.exe: Error response from daemon: Get https://registry-1.docker.io/v2/library/hello-world/manifests/latest: unauthorized: incorrect username or password.
See &apos;C:\Program Files\Docker\Docker\Resources\bin\docker.exe run --help&apos;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That message: ‘Error response from daemon: Get https://registry-1.docker.io/v2/library/hello-world/manifests/latest: unauthorized: incorrect username or password.’ can send you down a wild goose-chase to figure out what is wrong!!!
&lt;!-- markdown-link-check-enable --&gt;&lt;/p&gt;

&lt;p&gt;Since it tells you the call back to the docker registry is not authorized, you think you need to login again, even though Docker tells you, you are logged in…. Hmm, already strange, let’s try that nonetheless.&lt;/p&gt;

&lt;h1 id=&quot;logging-in-again&quot;&gt;Logging in again&lt;/h1&gt;
&lt;p&gt;If I run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker login&lt;/code&gt;, it shows me the currently logged in user, which I did via the login interface after the installation:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190116/2019_01_16_Docker_For_Windows_Login.png&quot; alt=&quot;&quot; /&gt;
Notice that I am using my e-mail address here. The login is just fine:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190116/2019_01_16_Docker_for_Windows_Email_Logged_In.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Still, calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker run hello-world&lt;/code&gt; gives me the same error:
&lt;!-- markdown-link-check-disable --&gt;&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;C:\Users\RobBos&amp;gt; docker run hello-world
Unable to find image &apos;hello-world:latest&apos; locally
C:\Program Files\Docker\Docker\Resources\bin\docker.exe: Error response from daemon: Get https://registry-1.docker.io/v2/library/hello-world/manifests/latest unauthorized: incorrect username or password.
See &apos;C:\Program Files\Docker\Docker\Resources\bin\docker.exe run --help&apos;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;!-- markdown-link-check-enable --&gt;

&lt;p&gt;After testing all sorts of stuff, reinstalling Docker (reboots required) and searching around some more, I &lt;em&gt;finally&lt;/em&gt; got to a comment somewhere on another issue… And the issue is…. I am logged in with my &lt;strong&gt;e-mail&lt;/strong&gt; address and &lt;strong&gt;not&lt;/strong&gt; with my “insert curse” username!&lt;/p&gt;

&lt;h1 id=&quot;fixing-it&quot;&gt;Fixing it&lt;/h1&gt;
&lt;p&gt;Switching to use the &lt;strong&gt;username&lt;/strong&gt; in Docker will switch the login for the session, and all of a sudden it just works!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2019/20190116/2019_01_16_Docker_for_windows_logged_in_user.png&quot; alt=&quot;&quot; /&gt;
Very odd that docker does accept both for the login here, while the e-mail address is not working. Somebody probably enabled that for the web front-end and didn’t test the CLI.&lt;/p&gt;

&lt;p&gt;Maybe someone else will find this post when hitting the same issue and it saves them a lot of time!&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Missing Azure Functions Logs</title>
			<link href="https://devopsjournal.io/blog/2019/01/13/Missing-Azure-Functions-Logs"/>
			<updated>2019-01-13T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2019/01/13/Missing-Azure-Functions-Logs</id>
			<content type="html">&lt;p&gt;I was testing with our Azure Function and had set the cron expression on the timer trigger to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;0 0 */2 * * *&quot;&lt;/code&gt;, based on the example from the Microsoft &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-timer#cron-expressions?WT.mc_id=AZ-MVP-5003719&quot;&gt;documentation&lt;/a&gt;. When I went to the logs a day later, I noticed that some of the runs weren’t there!
&lt;img src=&quot;/images/2019/20190113/emily-morter-188019-unsplash.jpg&quot; alt=&quot;&quot; /&gt;
&lt;a style=&quot;background-color:black;color:white;text-decoration:none;padding:4px 6px;font-family:-apple-system, BlinkMacSystemFont, &amp;quot;San Francisco&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Ubuntu, Roboto, Noto, &amp;quot;Segoe UI&amp;quot;, Arial, sans-serif;font-size:12px;font-weight:bold;line-height:1.2;display:inline-block;border-radius:3px&quot; href=&quot;https://unsplash.com/@emilymorter?utm_medium=referral&amp;amp;utm_campaign=photographer-credit&amp;amp;utm_content=creditBadge&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; title=&quot;Download free do whatever you want high-resolution photos from Emily Morter&quot;&gt;&lt;span style=&quot;display:inline-block;padding:2px 3px&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; style=&quot;height:12px;width:auto;position:relative;vertical-align:middle;top:-2px;fill:white&quot; viewBox=&quot;0 0 32 32&quot;&gt;&lt;title&gt;unsplash-logo&lt;/title&gt;&lt;path d=&quot;M10 9V0h12v9H10zm12 5h10v18H0V14h10v9h12v-9z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;&lt;span style=&quot;display:inline-block;padding:2px 3px&quot;&gt;Photo by Emily Morter&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;missing-logs-&quot;&gt;Missing logs ??&lt;/h2&gt;
&lt;p&gt;I added a red line were I noticed some of the logs missing.
&lt;img src=&quot;/images/2019/20190113/20190113_01_Every_2_hours.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;At first, I thought that the trigger wasn’t firing, or maybe something was wrong with my cron expression. I tested several other expressions and redeployed the function, but to no avail.&lt;/p&gt;

&lt;h2 id=&quot;the-search&quot;&gt;The search&lt;/h2&gt;
&lt;p&gt;Eventually, I found a comment deep down in a &lt;a href=&quot;https://github.com/Azure/azure-functions-host/issues/1534#issuecomment-427922955&quot;&gt;GitHub issue&lt;/a&gt; that actually pointed me in the right direction!&lt;/p&gt;

&lt;h2 id=&quot;application-insights-sampling&quot;&gt;Application Insights Sampling&lt;/h2&gt;
&lt;p&gt;The logs you see in an Azure function are provided by Application Insights. Due to large data ingestion during our testing period (we had the trigger fire every minute to test with), I had enabled &lt;strong&gt;sampling&lt;/strong&gt; on the Application Insights instance! That change was made after seeing a bill going upwards of € 500,- during the testing period 😄.&lt;/p&gt;

&lt;h2 id=&quot;finding-the-sampling-setting&quot;&gt;Finding the sampling setting&lt;/h2&gt;
&lt;p&gt;To correct the sampling settings (running once every two hours is significantly less data then every minute!), you need to go to the Application Insights instance.&lt;/p&gt;

&lt;p&gt;Go to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Usage and estimated costs&lt;/code&gt; and click on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data sampling&lt;/code&gt; button:
&lt;img src=&quot;/images/2019/20190113/20190113_03_Settings.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You can now change the sampling setting:
&lt;img src=&quot;/images/2019/20190113/20190113_02_Sampling.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Wait for a couple of runs to execute and you can verify that it now shows all the logs again:
&lt;img src=&quot;/images/2019/20190113/20190113_04_Fixed.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Azure Functions Connection Monitoring</title>
			<link href="https://devopsjournal.io/blog/2018/10/24/azure-functions-connectionlimit-monitoring"/>
			<updated>2018-10-24T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2018/10/24/azure-functions-connectionlimit-monitoring</id>
			<content type="html">&lt;p&gt;Last week I noticed our Azure Function wasn’t running anymore and I got a pop-up in the &lt;a href=&quot;https://portal.azure.com&quot;&gt;Azure Portal&lt;/a&gt; indicating that we reached the limit on our open connections. The popup message contains something like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Azure Host thresholds exceeded: [Connections]&lt;/code&gt; and links to this &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-functions/manage-connections?WT.mc_id=DOP-MVP-5003719&quot;&gt;documentation page&lt;/a&gt;. The documentation already hints at the usual suspects: HttpClient holds on to the connections longer then you’ll usually need. Since the whole Azure Functions sandbox has several hard limits, usage of an HttpClient in the default pattern is a common way to hit the Connection Count limit. The documentation also notes an example for a DocumentClient and SqlClient, although the latter already uses connection pooling.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2018/20181024/2018_10_24_nick-seliverstov-516549-unsplash.jpg&quot; alt=&quot;Header image&quot; /&gt;&lt;br /&gt;
&lt;a style=&quot;background-color:black;color:white;text-decoration:none;padding:4px 6px;font-family:-apple-system, BlinkMacSystemFont, &amp;quot;San Francisco&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Ubuntu, Roboto, Noto, &amp;quot;Segoe UI&amp;quot;, Arial, sans-serif;font-size:12px;font-weight:bold;line-height:1.2;display:inline-block;border-radius:3px&quot; href=&quot;https://unsplash.com/@slvrstvk?utm_medium=referral&amp;amp;utm_campaign=photographer-credit&amp;amp;utm_content=creditBadge&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; title=&quot;Download free do whatever you want high-resolution photos from NICK SELIVERSTOV&quot;&gt;&lt;span style=&quot;display:inline-block;padding:2px 3px&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; style=&quot;height:12px;width:auto;position:relative;vertical-align:middle;top:-1px;fill:white&quot; viewBox=&quot;0 0 32 32&quot;&gt;&lt;title&gt;unsplash-logo&lt;/title&gt;&lt;path d=&quot;M20.8 18.1c0 2.7-2.2 4.8-4.8 4.8s-4.8-2.1-4.8-4.8c0-2.7 2.2-4.8 4.8-4.8 2.7.1 4.8 2.2 4.8 4.8zm11.2-7.4v14.9c0 2.3-1.9 4.3-4.3 4.3h-23.4c-2.4 0-4.3-1.9-4.3-4.3v-15c0-2.3 1.9-4.3 4.3-4.3h3.7l.8-2.3c.4-1.1 1.7-2 2.9-2h8.6c1.2 0 2.5.9 2.9 2l.8 2.4h3.7c2.4 0 4.3 1.9 4.3 4.3zm-8.6 7.5c0-4.1-3.3-7.5-7.5-7.5-4.1 0-7.5 3.4-7.5 7.5s3.3 7.5 7.5 7.5c4.2-.1 7.5-3.4 7.5-7.5z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;&lt;span style=&quot;display:inline-block;padding:2px 3px&quot;&gt;NICK SELIVERSTOV&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;searching-around&quot;&gt;Searching around&lt;/h2&gt;
&lt;p&gt;When searching for this pattern, you can find a lot of examples that show you how the HttpClient does things and how to fix it (declare the client as static so it isn’t disposed on every function execution). You can even find examples from &lt;a href=&quot;https://www.troyhunt.com/breaking-azure-functions-with-too-many-connections/&quot;&gt;Troy Hunt&lt;/a&gt; and from the &lt;a href=&quot;https://blogs.msdn.microsoft.com/visualstudioalmrangers/2018/04/03/how-we-checked-and-fixed-the-503-error-and-performance-issue-in-our-azure-function/&quot;&gt;ALM Rangers&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;monitoring&quot;&gt;Monitoring&lt;/h2&gt;
&lt;p&gt;Since we are using the &lt;a href=&quot;https://github.com/Azure/azure-libraries-for-net&quot;&gt;Azure Fluent SDK&lt;/a&gt; to retrieve information from an Azure Subscription, and that instantiates several HttpClients inside of it, I wanted to start monitoring the connection count first, to see if it was a gradual ramp up (first time finding it was 24 hours &lt;strong&gt;after&lt;/strong&gt; deployment of a change), or something else.&lt;/p&gt;

&lt;p&gt;Monitoring would give a better idea overall about the issue and it’s part of the DevOps practice: you cannot improve things if you aren’t monitoring them first.&lt;/p&gt;

&lt;h2 id=&quot;searching-around-for-monitoring&quot;&gt;Searching around for monitoring&lt;/h2&gt;
&lt;p&gt;Since I couldn’t directly find how to monitor the connection count, I even opened an &lt;a href=&quot;https://github.com/MicrosoftDocs/azure-docs/issues/17205#issuecomment-432484636&quot;&gt;issue&lt;/a&gt; on the documentation repository to see if someone could help. Sure enough, someone responded with the location where to find the metric.&lt;/p&gt;

&lt;p&gt;Since it took me a lot of searching and overlooking the first selection option, I am documenting the full process here.&lt;/p&gt;

&lt;h2 id=&quot;finding-the-metric&quot;&gt;Finding the metric&lt;/h2&gt;
&lt;p&gt;Of course, the information is only available when you are running &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/application-insights/app-insights-overview?WT.mc_id=AZ-MVP-5003719&quot;&gt;Application Insights&lt;/a&gt;.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Go to the Application Insights instance connected to the Function&lt;/li&gt;
  &lt;li&gt;Go to ‘metrics’&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Change the resource&lt;/strong&gt; from your ‘Application Insights’ instance to ‘App Service’ to connect to the App Service that is hosting the function.&lt;/li&gt;
  &lt;li&gt;There is only one ‘metric namespace’ to choose from, and it is already selected.&lt;/li&gt;
  &lt;li&gt;Select the ‘Connections’ metric:
&lt;img src=&quot;/images/2018/20181024/2018_10_24_01_Metrics.png&quot; alt=&quot;Azure Metrics&quot; /&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;fixing-the-used-connection-count&quot;&gt;Fixing the used connection count&lt;/h2&gt;
&lt;p&gt;After monitoring we changed the instantiation of the clients we used from the Azure Fluent SDK to static instances and you can see that the connection count has improved a lot:
&lt;img src=&quot;/images/2018/20181024/2018_10_24_02_Metrics.png&quot; alt=&quot;Improvement&quot; /&gt;&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>SonarQube setup for Azure DevOps</title>
			<link href="https://devopsjournal.io/blog/2018/10/20/SonarQube-setup"/>
			<updated>2018-10-20T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2018/10/20/SonarQube-setup</id>
			<content type="html">&lt;p&gt;During installation and setting up a &lt;a href=&quot;https://www.sonarqube.org/&quot;&gt;SonarQube&lt;/a&gt; server for usage in an Azure DevOps Build I found some things that I didn’t remember from previous installation and wanted to log that in this post, so the next time I have somewhere to find these things in one place.&lt;/p&gt;

&lt;h2 id=&quot;updated-5-1-2019&quot;&gt;Updated 5-1-2019&lt;/h2&gt;
&lt;p&gt;Read the last section of this post if you want to use an even easier way of setting up and maintaining SonarQube: run it behind an Azure App Service!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2018/20181020/2018_10_20_Zach_Lucero_Dog.png&quot; alt=&quot;Cool dog by Zach Lucero&quot; /&gt;&lt;br /&gt;
&lt;a style=&quot;background-color:black;color:white;text-decoration:none;padding:4px 6px;font-family:-apple-system, BlinkMacSystemFont, &amp;quot;San Francisco&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Ubuntu, Roboto, Noto, &amp;quot;Segoe UI&amp;quot;, Arial, sans-serif;font-size:12px;font-weight:bold;line-height:1.2;display:inline-block;border-radius:3px&quot; href=&quot;https://unsplash.com/@zlucerophoto?utm_medium=referral&amp;amp;utm_campaign=photographer-credit&amp;amp;utm_content=creditBadge&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; title=&quot;Download free do whatever you want high-resolution photos from Zach Lucero&quot;&gt;&lt;span style=&quot;display:inline-block;padding:2px 3px&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; style=&quot;height:12px;width:auto;position:relative;vertical-align:middle;top:-1px;fill:white&quot; viewBox=&quot;0 0 32 32&quot;&gt;&lt;title&gt;unsplash-logo&lt;/title&gt;&lt;path d=&quot;M20.8 18.1c0 2.7-2.2 4.8-4.8 4.8s-4.8-2.1-4.8-4.8c0-2.7 2.2-4.8 4.8-4.8 2.7.1 4.8 2.2 4.8 4.8zm11.2-7.4v14.9c0 2.3-1.9 4.3-4.3 4.3h-23.4c-2.4 0-4.3-1.9-4.3-4.3v-15c0-2.3 1.9-4.3 4.3-4.3h3.7l.8-2.3c.4-1.1 1.7-2 2.9-2h8.6c1.2 0 2.5.9 2.9 2l.8 2.4h3.7c2.4 0 4.3 1.9 4.3 4.3zm-8.6 7.5c0-4.1-3.3-7.5-7.5-7.5-4.1 0-7.5 3.4-7.5 7.5s3.3 7.5 7.5 7.5c4.2-.1 7.5-3.4 7.5-7.5z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;&lt;span style=&quot;display:inline-block;padding:2px 3px&quot;&gt;Zach Lucero&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;installation-on-azure&quot;&gt;Installation on Azure&lt;/h2&gt;
&lt;p&gt;For installation on an Azure environment I used the same &lt;a href=&quot;https://github.com/Azure/azure-quickstart-templates/tree/master/sonarqube-azuresql&quot;&gt;Azure QuickStart ARM template&lt;/a&gt; I used before. Somehow, each time I need this template, something has changed underneath and I get to &lt;a href=&quot;/blog/2018/08/12/self-signed-certificate-on-sonarqube-server&quot;&gt;fix the template&lt;/a&gt;. This time the download URL for the SonarQube installer was changed as well as a new version got released. This has now been included in the template: because it is open source, I could find the places that needed to be updated and send in a &lt;a href=&quot;https://github.com/Azure/azure-quickstart-templates/pull/5313&quot;&gt;pull request&lt;/a&gt; to Microsoft with the fix. I love open source! Such a pleasure to find an issue, look at the code and present a fix to the repository maintainer!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2018/20181020/2018_08_12_SonarQube.png&quot; alt=&quot;SonarQube logo&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You can follow the usual steps from the ARM template: download and install the Java Development Kit on the SonarQube server, restart the SonarQube service and you’re up and running with the &lt;strong&gt;server side&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;things-to-know-for-next-time&quot;&gt;Things to know for next time&lt;/h2&gt;
&lt;p&gt;There are a couple of things that you need to think of when starting an installation yourself. The ARM template is already a great help in it, but you need to think of some other things. Those are mostly client side, so on the Azure DevOps Agent.&lt;/p&gt;

&lt;h3 id=&quot;bring-a-valid-certificate&quot;&gt;Bring a valid certificate&lt;/h3&gt;
&lt;p&gt;As noted &lt;a href=&quot;/blog/2018/08/12/self-signed-certificate-on-sonarqube-server&quot;&gt;before&lt;/a&gt;, the template uses a self signed certificate, which will not work with Azure DevOps: the tasks from the marketplace need a valid certificate that it trusts for the connection with the server. Therefore you need to provide a valid certificate and setup a DNS entry to match the URL in the certificate.&lt;/p&gt;

&lt;h3 id=&quot;download-or-install-the-sonarqube-extension&quot;&gt;Download or install the SonarQube extension&lt;/h3&gt;
&lt;p&gt;Go to the &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=SonarSource.sonarqube&quot;&gt;marketplace&lt;/a&gt; and download or request installation in your Azure DevOps subscription.&lt;/p&gt;

&lt;h3 id=&quot;the-run-analysis-task-has-java-as-a-requirement&quot;&gt;The ‘Run Analysis Task’ has java as a requirement&lt;/h3&gt;
&lt;p&gt;This was a gotcha that I forgot this time: the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Run Analysis Task&lt;/code&gt; has a demand requirement that it needs Java (specifically the Java Runtime Environment 8.0) &lt;strong&gt;on the agent&lt;/strong&gt;. This also means that you cannot run it on a hosted agent: those do not have the JRE installed! Only the JDK is installed, which doesn’t add support for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;java&lt;/code&gt; demand. I raised an &lt;a href=&quot;https://github.com/actions/virtual-environments/pull/315&quot;&gt;issue&lt;/a&gt; on the hosted agent with a request for it.&lt;/p&gt;

&lt;h3 id=&quot;building-on-a-new-agent&quot;&gt;Building on a new agent?&lt;/h3&gt;
&lt;p&gt;When you have a new agent you could install &lt;a href=&quot;https://visualstudio.microsoft.com/downloads/&quot;&gt;Visual Studio Community edition&lt;/a&gt; on it, that will provide you with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;msbuild&lt;/code&gt; capability on the agent. This does &lt;strong&gt;not&lt;/strong&gt; give you the tools to run the unit tests on the server, which is needed for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Run unit test&lt;/code&gt; task. These tools will provide SonarQube with the necessary information it needs to do, well basically, anything! You can install a licensed Visual Studio Enterprise on it, but then you need to update that license every once in a while. You probably don’t want that, because of the occasional error it will give you, for which you need to login to the server.&lt;/p&gt;

&lt;p&gt;To help fix this, you can use the &lt;a href=&quot;https://www.nuget.org/packages/Microsoft.TestPlatform&quot;&gt;Visual Studio Test Platform Installer&lt;/a&gt; task to install all the tools VSTest needs to run.&lt;/p&gt;

&lt;h3 id=&quot;want-css-analysis-from-sonarqube&quot;&gt;Want CSS analysis from SonarQube&lt;/h3&gt;
&lt;p&gt;Out of the box, SonarQube can scan your CSS files for issues with over 180 available &lt;a href=&quot;https://github.com/racodond/sonar-css-plugin#available-rules&quot;&gt;rules&lt;/a&gt;. To do this, the agent needs to have &lt;a href=&quot;https://nodejs.org/en/download/&quot;&gt;Node.js&lt;/a&gt; installed. This is already available on a hosted agent, but you cannot use that yet because of the dependency on JRE!&lt;/p&gt;

&lt;h3 id=&quot;sonarqube-css-issue-on-large-solution&quot;&gt;SonarQube CSS issue on large solution&lt;/h3&gt;
&lt;p&gt;Currently there is an &lt;a href=&quot;https://community.sonarsource.com/t/sonarqube-post-processing-fails-with-unknown-reason/1798/6&quot;&gt;issue&lt;/a&gt; on SonarQube with larger solutions or CSS files. The process seems to run out of memory somewhere. In the Azure DevOps Build log, you’ll see these as the last steps being logged:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;INFO: Quality profile for cs: Sonar way
INFO: Quality profile for css: Sonar way
INFO: Quality profile for js: Sonar way
INFO: Quality profile for xml: Sonar way
INFO: Sensor SonarCSS Metrics [cssfamily]
WARNING: WARN: Metric &apos;comment_lines_data&apos; is deprecated. Provided value is ignored.
INFO: Sensor SonarCSS Metrics [cssfamily] (done) | time=1937ms
INFO: Sensor SonarCSS Rules [cssfamily]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For now the recommended fix is: &lt;strong&gt;do not use the CSS analysis&lt;/strong&gt;, which isn’t great, but better then the alternative: currently the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Run Analysis Task&lt;/code&gt; just hangs until the maximum runtime of your build has been reached. Al that time, your build server will run at 100% CPU (if you have 1 CPU available, 2 CPU’s got me 50% utilization)!
It took me quite some searching around to find this one, so it’s good to document it here.&lt;/p&gt;

&lt;p&gt;The current fix is to start the analysis task with a parameter that redirects the CSS files to a non-existing analyzer:&lt;br /&gt;
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;d:sonar.css.file.suffixes=.foo&lt;/code&gt; or do that globally for your entire SonarQube server via the settings on the CSS analysis there (which would be easier if you have multiple projects with this issue).&lt;/p&gt;

&lt;h1 id=&quot;update-05-01-2019-run-it-in-an-azure-app-service&quot;&gt;Update (05-01-2019) Run it in an Azure App Service!&lt;/h1&gt;
&lt;!-- markdown-link-check-disable --&gt;
&lt;p&gt;&lt;a href=&quot;https://www.linkedin.com/in/nathan-vanderby-92a19814/&quot;&gt;Nathan Vanderby&lt;/a&gt;, a Premier Field Engineer from Microsoft, has 
&lt;!-- markdown-link-check-enable --&gt;
created an ARM template to run the SonarQube installation behind an Azure App Service with a Java host. This saves you a lot of steps mentioned above! You can find the scripts for it on&lt;a href=&quot;https://github.com/vanderby/SonarQube-AzureAppService&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Several benefits you get this way:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;You don’t have to manage a Virtual Machine anymore.&lt;/li&gt;
  &lt;li&gt;Setting up SSL becomes easier, thanks to the Azure App Service SSL being simpler.&lt;/li&gt;
  &lt;li&gt;Using a KeyVault for your secrets is now an option, you can inject those values as environment options.&lt;/li&gt;
  &lt;li&gt;If you already have an App Service Plan, you can use that for hosting and cut down on your resources and cost (although, running on a burstable VM isn’t that expensive)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The biggest benefit is that the App Service already has Java installed! So no more downloading the JRE by hand, installing it manually and starting the SonarQube service by hand!&lt;/p&gt;

&lt;p&gt;Setting up a new SonarQube server this way is a breeze. Updating it should be easier as well and that is the another big plus: you now have the ability to run the installation on a &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/app-service/deploy-staging-slots?WT.mc_id=AZ-MVP-5003719&quot;&gt;Deployment Slot&lt;/a&gt;, let it update the database and then switch to the slot. No manual updating anymore!&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Azure DevOps Pipeline for GitHub Open Source Projects</title>
			<link href="https://devopsjournal.io/blog/2018/09/10/GitHub-Azure-DevOps-Pipeline"/>
			<updated>2018-09-10T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2018/09/10/GitHub-Azure-DevOps-Pipeline</id>
			<content type="html">&lt;p&gt;Microsoft &lt;a href=&quot;https://azure.microsoft.com/en-us/blog/introducing-azure-devops/?WT.mc_id=DOP-MVP-5003719&quot;&gt;announced&lt;/a&gt; today that they have a ‘new’ product: Azure DevOps! With that announcement came another one: Azure DevOps pipelines for GitHub open source projects with unlimited minutes! I wanted to see what the integration with GitHub would look like, so I tried it out.&lt;/p&gt;

&lt;p&gt;Note: of course, you could already create pipelines for GitHub repo’s, but only inside of a VSTS account and not &lt;strong&gt;with unlimited build/release minutes!&lt;/strong&gt; If you had you own private agent installation, you could build with that.&lt;/p&gt;

&lt;p&gt;Now all open source projects can utilize the Azure DevOps pipelines!&lt;/p&gt;

&lt;p&gt;Here are the steps you’d take to add a new pipeline from a GitHub repository.&lt;/p&gt;

&lt;p&gt;From your GitHub repo, go to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MarketPlace&lt;/code&gt; and search for Azure. The marketplace entry is named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Azure Pipelines&lt;/code&gt;: &lt;br /&gt;
&lt;img src=&quot;/images/2018/20180910/2018_09_10-01-GitHub-Marketplace.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Click on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Set up a plan&lt;/code&gt;:&lt;br /&gt;
&lt;img src=&quot;/images/2018/20180910/2018_09_10-02-Setup-a-plan.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Choose &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Free&lt;/code&gt;:&lt;br /&gt;
&lt;img src=&quot;/images/2018/20180910/2018_09_10-03-Azure-Pipelines.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Choose to what repositories you want to enable a pipeline for. I have picked a &lt;a href=&quot;https://github.com/rajbos/VSTSClient&quot;&gt;.NET tool&lt;/a&gt; that I created to talk to VSTS.&lt;/p&gt;

&lt;p&gt;One more authentication in GitHub to make sure you have the rights to enable the market place integration and you get send to Azure DevOps. &lt;br /&gt;
&lt;img src=&quot;/images/2018/20180910/2018_09_10-04-Installing-Azure-Pipelines.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You are now in an editor to setup a new Azure DevOps account. If you already have one, you can choose that: 
&lt;img src=&quot;/images/2018/20180910/2018_09_10-05-Create-Azure-DevOps-project.png&quot; alt=&quot;&quot; /&gt;&lt;br /&gt;
Next, Azure DevOps starts creating your project. &lt;br /&gt;
&lt;img src=&quot;/images/2018/20180910/2018_09_10-06-Signup.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It will ask you which repository you want to use.&lt;br /&gt;
&lt;img src=&quot;/images/2018/20180910/2018_09_10-07-New-pipeline-Pipelines.png&quot; alt=&quot;&quot; /&gt;   &lt;br /&gt;
It detects the kind of project in the repository and gives you a default option based on that. You can choose other options, but I’ll just go with the .NET desktop build for this project, because that is what this project is.&lt;br /&gt;
&lt;img src=&quot;/images/2018/20180910/2018_09_10-08-New-pipeline-Pipelines.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;A yaml file is created for your (named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;azure-pipelines.yml&lt;/code&gt;) which will be saved inside of your repository: that also means you now have a two-way binding between GitHub and Azure DevOps: it can also write changes back to the GitHub repository. 
&lt;img src=&quot;/images/2018/20180910/2018_09_10-10-New-pipeline.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Save and run&lt;/code&gt; does exactly what is says, and if left with the default setting, will commit the new yml file to the chosen repository:  &lt;br /&gt;
&lt;img src=&quot;/images/2018/20180910/2018_09_10-11-New-pipeline.png&quot; alt=&quot;&quot; /&gt;&lt;br /&gt;
Now we have a running pipeline (free of charge! how nice is that!).  &lt;br /&gt;
&lt;img src=&quot;/images/2018/20180910/2018_09_10-11-Running.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;After the build is done and it succeeded, you get a summary page:&lt;br /&gt;
&lt;img src=&quot;/images/2018/20180910/2018_09_10-12-rajbos.VSTSClient.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;status-badge&quot;&gt;Status badge&lt;/h2&gt;
&lt;p&gt;If you want to show your build status on a project page, you can also get the build status badge for the project, right from Azure DevOps.&lt;br /&gt;
Note: you could do that for any project before.&lt;/p&gt;

&lt;p&gt;Go to the build definition, find the menu, go to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;status badge&lt;/code&gt;:  &lt;br /&gt;
&lt;img src=&quot;/images/2018/20180910/2018_09_10-12-Status-Badge-Create.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;And copy the necessary code/markdown into the place you want to show it.&lt;br /&gt;
&lt;img src=&quot;/images/2018/20180910/2018_09_10-12-Status-badge.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;And the end result inside the readme of my repository:&lt;br /&gt;
&lt;img src=&quot;/images/2018/20180910/2018_09_10-View-badge.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;email-notification&quot;&gt;Email notification&lt;/h2&gt;
&lt;p&gt;Because I have an email address connected to my GitHub account, I also receive pipeline notifications by email:&lt;br /&gt;
&lt;img src=&quot;/images/2018/20180910/2018_09_10-Email-notification.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;triggers&quot;&gt;Triggers&lt;/h2&gt;
&lt;p&gt;You have all the regular options for your pipeline available. For example: the new Azure DevOps project has been provisioned with a continuous integration trigger: this way, each commit will trigger a new build and will let the committer know how the build went.&lt;br /&gt;
&lt;img src=&quot;/images/2018/20180910/2018_09_10-13-Triggers.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;want-more&quot;&gt;Want more?&lt;/h2&gt;
&lt;p&gt;Since you now have an Azure DevOps project available, you can also enable other features for this project. This way you can leverage the powerful options Azure DevOps gives you.&lt;/p&gt;

&lt;p&gt;Go to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Project settings&lt;/code&gt;–&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Services&lt;/code&gt; and enable the services you like to use.
&lt;img src=&quot;/images/2018/20180910/2018_09_10-15Settings-Services.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Reload the page and the enabled services are available. Here I enabled the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Boards&lt;/code&gt; service: &lt;br /&gt;
&lt;img src=&quot;/images/2018/20180910/2018-09_10-Work-Items-Boards.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Azure error setting up export from Activity Log to Event Hub</title>
			<link href="https://devopsjournal.io/blog/2018/09/05/Azure-error-setting-up-export-from-activity-log-to-eventhub"/>
			<updated>2018-09-05T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2018/09/05/Azure-error-setting-up-export-from-activity-log-to-eventhub</id>
			<content type="html">&lt;p&gt;While working to setup an export from Activity Log to an Event Hub I got no response on a save action. This took some time to figure out why this happened, so I thought it could be helpful for someone else.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2018/20180905/adam-solomon-472458-unsplash.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h4 id=&quot;photo-by-adam-solomon-on-unsplash&quot;&gt;Photo by &lt;a href=&quot;https://unsplash.com/photos/WHUDOzd5IYU&quot;&gt;Adam Solomon on Unsplash&lt;/a&gt;&lt;/h4&gt;

&lt;h2 id=&quot;issue-when-saving&quot;&gt;Issue when saving&lt;/h2&gt;
&lt;p&gt;When saving the export setting via this blade:&lt;br /&gt;
&lt;img src=&quot;/images/2018/20180905/2018_09_05_Export_activity_log_failure_setup.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I got this error:&lt;br /&gt;
&lt;img src=&quot;/images/2018/20180905/2018_09_05_Export_activity_log_failure_setup_notification.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;After scratching my head a little I checked the browsers console log:&lt;br /&gt;
&lt;img src=&quot;/images/2018/20180905/2018_09_05_Export_activity_log_failure_setup_consolelog.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Well, what do you know! Apparently the resource provider &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;microsoft.insights&lt;/code&gt; hasn’t been registered yet! Would have been a nice message inside of the Portal itself, but at least now I can fix it!&lt;/p&gt;

&lt;h2 id=&quot;the-fix-using-the-portal&quot;&gt;The fix using the portal&lt;/h2&gt;
&lt;p&gt;Go to your subscriptions, pick the correct one and navigate to resource providers:&lt;br /&gt;
&lt;img src=&quot;/images/2018/20180905/2018_09_05_Export_activity_log_failure_setup_register.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Register the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;microsoft.insights&lt;/code&gt; provider and save the export option again. Problem solved!&lt;/p&gt;

&lt;h2 id=&quot;the-fix-using-powershell&quot;&gt;The fix using PowerShell&lt;/h2&gt;
&lt;p&gt;You can also fix this via PowerShell, as you can read on &lt;a href=&quot;https://pascalnaber.wordpress.com/2017/05/30/fixing-the-subscription-is-not-registered-to-use-namespace-microsoft-xxx/&quot;&gt;Pascal Nabers&lt;/a&gt; blog.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>GDBC DevOps pipelines in VSTS</title>
			<link href="https://devopsjournal.io/blog/2018/09/02/GDBC-DevOps-Pipelines"/>
			<updated>2018-09-02T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2018/09/02/GDBC-DevOps-Pipelines</id>
			<content type="html">&lt;h2 id=&quot;global-devops-bootcamp&quot;&gt;Global DevOps BootCamp&lt;/h2&gt;
&lt;p&gt;In June 2018 I was part of the team behind &lt;a href=&quot;https://www.globaldevopsbootcamp.com/&quot;&gt;Global DevOps BootCamp&lt;/a&gt; (or GDBC in short). The goal of the boot camp is to create a world wide event where everyone could get a taste of DevOps on the Microsoft Stack. It is an amazing combination between getting your hands dirty and sharing experience and knowledge around VSTS, Azure, DevOps with other community members. This years edition was great, with 75 registered venues worldwide and 8.000 registered attendees! The feedback we’ve got was wonderful: a true community event where everybody was asking for more!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2018/20180902/2018_08_31_Unsplash.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h4 id=&quot;photo-by-rawpixel-on-unsplash&quot;&gt;Photo by &lt;a href=&quot;https://unsplash.com/&quot;&gt;rawpixel on Unsplash&lt;/a&gt;&lt;/h4&gt;

&lt;h2 id=&quot;challenges&quot;&gt;Challenges&lt;/h2&gt;
&lt;p&gt;During the GDBC participants could try to complete 60+ challenges that the GDBC team had created. Those challenges where created by a team of more than 15 people (global organizers and venue organizers helped out), so they had quite a lot of changes during the preparation period. If you want to see and dive into the challenges, you can! The challenges have been open sourced for the community and can be found here: &lt;a href=&quot;https://github.com/XpiritBV/GDBC2018-Challenges&quot;&gt;GitHub/XpiritBV/GDBC2018-Challenges&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The challenges would be created as work items inside a VSTS team, where participants would be assigned to, so they could collaborate on the challenges there. By dragging the work items to the “done” column, they’d indicated that they finished the challenge and received points for it, which they could follow on a leaderboard (more info in this &lt;a href=&quot;/blog/2018/06/16/GDBC-DevOps-on-the-Leaderboard&quot;&gt;post&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;pipelines-for-the-challenges&quot;&gt;Pipelines for the Challenges&lt;/h2&gt;
&lt;p&gt;Early on, it was decided to save all the challenges in a git repository in VSTS, practicing what we wanted the participants to use (and because we are nerds!).
The challenge definitions where saved as markdown files, with a yaml header to indicate if the challenge was a bonus challenge, if the challenge had additional help available and other properties. In 2017, the team only found out issues with the templates when the organizers ran their scripts to create the actual work items in VSTS.&lt;/p&gt;

&lt;p&gt;To help the team with this, I started setting up a build and release pipeline in VSTS. Since there were a lot of moving parts with a complete team helping out, it would be helpful to run several checks during the changes to make sure all challenges where setup correctly.&lt;/p&gt;

&lt;p&gt;Eventually I even created another pipeline to push the changes into a VSTS team, so the challenge maintainers could see the end result as soon as possible. All tThis really helped fixing issues quickly.&lt;/p&gt;

&lt;h2 id=&quot;pipeline-on-push&quot;&gt;Pipeline on push&lt;/h2&gt;
&lt;p&gt;We had a tool available to convert the markdown, read the yaml headers,  parse all links and images in the markdown and eventually create work items from that. After adding additional checks to it, it could be used to check the challenges for completeness.
The tool, some PowerShell scripts, the challenges all had there own git repositories, so we could plug them in their own build pipelines: The tool was .NET core, the challenges would be published as artifacts and the same for the PowerShell scripts. You can see them linked as dependencies in the screenshot below.
&lt;img src=&quot;/images/2018/20180902/2018_08_31_Release.png&quot; alt=&quot;Check challenges from PR&quot; /&gt;&lt;br /&gt;
In the environment was just one task: run the checks from the .NET core tooling:
&lt;img src=&quot;/images/2018/20180902/2018_08_31_ReleasePR_Detail.png&quot; alt=&quot;PR Detail&quot; /&gt;&lt;br /&gt;
The trigger for this was a pull request (PR) being created for the challenges repository, so the person creating the PR would get an email indicating the level of correctness of the challenges.&lt;/p&gt;

&lt;h2 id=&quot;pipeline-to-production&quot;&gt;Pipeline to production&lt;/h2&gt;
&lt;p&gt;After the pull request was checked by the pipeline described above, then merged into master, the second pipeline would be triggered. This time a couple more steps where involved.&lt;/p&gt;

&lt;p&gt;The artifacts where the same: &lt;br /&gt;
&lt;img src=&quot;/images/2018/20180902/2018_08_31_ReleasePipeline.png&quot; alt=&quot;Release to production&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The tasks where as follows:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2018/20180902/2018_08_31_ReleasePipelineDetails.png&quot; alt=&quot;Provision stories&quot; /&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Convert the markdown file to json&lt;br /&gt;
This is the same tool that does perform the checks and saves a json for easy formatting.&lt;/li&gt;
  &lt;li&gt;Zip up the help directories&lt;br /&gt;
The help needs to be a link to a single file that the participants could request and open when they needed it.&lt;/li&gt;
  &lt;li&gt;Save the zip files into a DropBox share and get unique links for them.
The unique link couldn’t be guessed, to prevent anyone from cheating.&lt;/li&gt;
  &lt;li&gt;Update the database for the scoreboard, with the correct points and help links for the challenges.&lt;/li&gt;
  &lt;li&gt;Clear the test team’s sprint from previous versions of the work items.&lt;/li&gt;
  &lt;li&gt;Create new work items based on the new challenge content.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After this pipeline completed, the whole team could check the end result inside of VSTS, by checking the setup for the test team.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2018/20180902/2018_08_31_TestTeamWorkItems.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;These pipelines helped the team find issues early on, so we could make sure the quality would be where we wanted it to be.&lt;/p&gt;

&lt;p&gt;Hopefully this post gives you more insights into some of the work we did to help a team out. If there are parts of this pipeline that you’d want more detail of, please let me know.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Retrieving AppSettings for an App Service with the Fluent SDK</title>
			<link href="https://devopsjournal.io/blog/2018/08/23/Retrieve-AppSettings-for-an-App-Service-with-the-Fluent-SDK"/>
			<updated>2018-08-23T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2018/08/23/Retrieve-AppSettings-for-an-App-Service-with-the-Fluent-SDK</id>
			<content type="html">&lt;p&gt;I am using the &lt;a href=&quot;https://azure.microsoft.com/en-us/blog/simpler-azure-management-libraries-for-net/?WT.mc_id=AZ-MVP-5003719&quot;&gt;Azure Fluent SDK&lt;/a&gt; to retrieve information about the Azure setup and I wanted to retrieve the AppSettings from an App Service (or function app, or logic app). The simple solution didn’t work and 
searching around didn’t reveal any information about it. Finding something that did work (initial testing with a different service principle didn’t change the results), so here we are…&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2018/20180823/2018_08_23_Research.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h6 id=&quot;photo-by-osman-rana-on-unsplash&quot;&gt;Photo by &lt;a href=&quot;https://unsplash.com/&quot;&gt;Osman Rana on Unsplash&lt;/a&gt;&lt;/h6&gt;

&lt;h2 id=&quot;expected-simple-solution&quot;&gt;Expected simple solution&lt;/h2&gt;
&lt;p&gt;Because all other information I needed came from a WebApp, retrieved by the default call, I expected that loading the AppSettings from the SiteConfig would deliver all settings:&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;IAzure&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AzureConnection&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GetAzureConnection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;webApp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AzureConnection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AppServices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WebApps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GetByIdAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;webAppResourceId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;settings&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;webApp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Inner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SiteConfig&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AppSettings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// settings is null because SiteConfig is null&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Unfortunately, the SiteConfig stays completely empty. As mentioned I checked and tested it with another service principle with more rights, but that wasn’t the issue. Searching around on the web didn’t yield any new information.&lt;/p&gt;

&lt;h3 id=&quot;research-the-http-traffic&quot;&gt;Research the Http traffic&lt;/h3&gt;
&lt;p&gt;So next I checked the Http traffic being send to see if the information came back from the HttpCall. [Note: the Fluent SDK is a wrapper around a generated HttpClient against the REST api’s]&lt;/p&gt;

&lt;h4 id=&quot;traffic-from-the-fluentsdk-calls&quot;&gt;Traffic from the FluentSDK calls&lt;/h4&gt;
&lt;p&gt;First up was seeing what Http calls where being made. For this I used &lt;a href=&quot;https://www.telerik.com/fiddler&quot;&gt;Fiddler&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;HttpRequests:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;GET /subscriptions/{SubscriptionId}/resourceGroups/{ResourceGroupName}/providers/Microsoft.Web/sites/WebAppName
GET /subscriptions/{SubscriptionId}/resourceGroups/{ResourceGroupName}/providers/Microsoft.Web/sites/{WebAppName}/config/web
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;You can see the loading of the WebApp information and next loading of the config for that WebApp.&lt;/p&gt;

&lt;h4 id=&quot;powershell&quot;&gt;PowerShell&lt;/h4&gt;
&lt;p&gt;Next I wanted to see if I could load the settings from a different stack, like e.g. PowerShell. Lo and behold, the PowerShell call &lt;strong&gt;did yield AppSettings!!!! WTF?!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;From PowerShell:&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$webApp&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Get-AzureRmwebApp&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-ResourceGroupName&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$resourceGroup&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$webAppName&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$webAppSettings&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$webApp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;SiteConfig&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AppSettings&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;HttpRequests:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;GET  /subscriptions/{SubscriptionId}/resourceGroups/{ResourceGroupName}/providers/Microsoft.Web/sites/WebAppName
GET  /subscriptions/{SubscriptionId}/resourceGroups/{ResourceGroupName}/providers/Microsoft.Web/sites/WebAppName/config/web
POST /subscriptions/{SubscriptionId}/resourceGroups/{ResourceGroupName}/providers/Microsoft.Web/sites/WebAppName/config/appsettings/list
POST /subscriptions/{SubscriptionId}/resourceGroups/{ResourceGroupName}/providers/Microsoft.Web/sites/WebAppName/config/connectionstrings/list
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Here you can see the same calls to the WebApp ad the Config, but then all of a sudden there are two &lt;strong&gt;other&lt;/strong&gt; calls into the config endpoint! You can see it’s loading both the AppSettings and the ConnectionStrings.&lt;/p&gt;

&lt;p&gt;Luckily the Fluent SDK is &lt;a href=&quot;https://github.com/Azure/azure-libraries-for-net&quot;&gt;open source&lt;/a&gt; (hurray for Microsoft!) so I started searching for the class that would handle the json that was being retrieved from the REST call.&lt;/p&gt;

&lt;h2 id=&quot;solution&quot;&gt;Solution&lt;/h2&gt;
&lt;p&gt;Searching in the Fluent SDK’s source code if eventually stumbled onto a new ManagementClient: the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WebSiteManagementClient&lt;/code&gt;, specifically helps with the settings for those websites!&lt;/p&gt;

&lt;p&gt;So the final code came down to this:&lt;/p&gt;
&lt;div class=&quot;language-c# highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WebSiteManagementClient&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;WebSiteManagementClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AzureCredentials&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SubscriptionId&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SubscriptionId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;settings&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WebSiteManagementClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WebApps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ListApplicationSettingsAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resourceGroupName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;appServiceName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;appSettings&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;settings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Properties&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
		</entry>
	
		<entry>
			<title>Using a self signed certificate on a SonarQube server with VSTS/TFS</title>
			<link href="https://devopsjournal.io/blog/2018/08/12/self-signed-certificate-on-sonarqube-server"/>
			<updated>2018-08-12T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2018/08/12/self-signed-certificate-on-sonarqube-server</id>
			<content type="html">&lt;p&gt;Recently I got a customer request to help them with provisioning a &lt;a href=&quot;https://www.sonarqube.org/&quot;&gt;SonarQube&lt;/a&gt; server hosted in Azure. Fortunately there is an &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-authoring-templates?WT.mc_id=AZ-MVP-5003719&quot;&gt;ARM template&lt;/a&gt; available for it: &lt;a href=&quot;https://github.com/Azure/azure-quickstart-templates/tree/master/sonarqube-azuresql&quot;&gt;link&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I ran into some issues with the ARM template at first and then tried to use the new SonarQube server within VSTS.&lt;/p&gt;

&lt;h2 id=&quot;tldr&quot;&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;I didn’t manage to get the SonarQube VSTS Tasks working with the self-signed certificate. I think it’s probably possible, but you’ll be much easier off&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2018/20180812/2018_08_12_SonarQube.png&quot; alt=&quot;SonarQube logo&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;arm-template-issues&quot;&gt;ARM template issues&lt;/h1&gt;
&lt;p&gt;At my first go with it, it took some time to figure out that the reason we couldn’t connect to it came from the way the self-signed certificate was created: the template didn’t create the certificate with a &lt;a href=&quot;https://en.wikipedia.org/wiki/Fully_qualified_domain_name&quot;&gt;fully qualified domain name&lt;/a&gt;. A couple of years ago that would’ve worked, but with the tighter security rules in browsers that doesn’t work anymore. Luckily I changed that with a small adjustment in the script and a &lt;a href=&quot;https://github.com/Azure/azure-quickstart-templates/pull/4692&quot;&gt;pull request&lt;/a&gt; later that problem has been fixed.&lt;/p&gt;

&lt;p&gt;A little later I found out that SonarQube updated their download links, deprecating the older TLS 1.2 versions which gave another issue. Another &lt;a href=&quot;https://github.com/Azure/azure-quickstart-templates/pull/4840&quot;&gt;pull request&lt;/a&gt; later and that is also fixed.&lt;/p&gt;

&lt;h1 id=&quot;java-installation&quot;&gt;Java installation&lt;/h1&gt;
&lt;p&gt;Now that those issues have been handled, you’ll probably find that you’ve missed the comments in the readme: the ARM template &lt;strong&gt;cannot&lt;/strong&gt; provision the Java JDK needed for installation, because Oracle will not let you download it from an open folder (like SonarQube does)!&lt;/p&gt;

&lt;p&gt;For this step you’ll need to RDP into the server and install the JDK by hand. &lt;br /&gt;
&lt;strong&gt;Do note&lt;/strong&gt;: don’t forget to change the default passwords for logging in to the SonarQube installation!!&lt;/p&gt;

&lt;p&gt;After that you’ll find out that the template provisions an IIS installation to host the SSL certificate and then be the proxy for the SonarQube server.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2018/20180812/2018_08_12_SonarQube_Project_page.png&quot; alt=&quot;SonarQube project page&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;using-the-sonarqube-server-in-vsts--tfs&quot;&gt;Using the SonarQube server in VSTS / TFS&lt;/h1&gt;
&lt;p&gt;When you have the server up and running, you’ll want to use it in VSTS. If you start adding the necessary steps to your build (find out more about it &lt;a href=&quot;https://docs.sonarqube.org/display/SCAN/Analyzing+with+SonarQube+Extension+for+VSTS-TFS&quot;&gt;here&lt;/a&gt;, you’ll find out that the builds will fail with some obscure messages connecting to the SonarQube server. If you are using a &lt;a href=&quot;https://docs.microsoft.com/en-us/vsts/pipelines/agents/agents?view=vsts#install?WT.mc_id=DOP-MVP-5003719&quot;&gt;private agent&lt;/a&gt;, you can log into the server and try to remediate these issues.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2018/20180812/2018_08_12_SonarQube_VSTS.png&quot; alt=&quot;SonarQube Tasks&quot; /&gt;&lt;/p&gt;

&lt;p&gt;First, you’ll hit it in the “Prepare analysis on SonarQube” step. Thinking it runs on the agent server on Windows, you can trust the server’s certificate in your local certificate store, using the &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-view-certificates-with-the-mmc-snap-in?WT.mc_id=AZ-MVP-5003719&quot;&gt;certificate snap-in&lt;/a&gt;. Double check the user the agent is running on or trust the certificate machine-wide.
Don’t forget to check your proxy configuration if you have one in between!&lt;/p&gt;

&lt;p&gt;Unfortunately: this doesn’t work! Nest, you double check and find out a part of this step actually creates a local Java Virtual Machine &lt;a href=&quot;https://en.wikipedia.org/wiki/Java_virtual_machine&quot;&gt;JVM&lt;/a&gt; that has its own version of the local certificate store. To add your own certificate in it, you can follow the steps from &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/connect/jdbc/configuring-the-client-for-ssl-encryption?view=sql-server-2017?WT.mc_id=DOP-MVP-5003719&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Next, you’ll find that NodeJS is used to send the requests to the SonarQube server! Great, now that also has its own version of the trust chain setup…&lt;/p&gt;

&lt;h1 id=&quot;end-result&quot;&gt;End result&lt;/h1&gt;
&lt;p&gt;And this is where we couldn’t figure out how to include the certificate here. Given the time that it already cost to get here and the expectation that a hosted agent could still be needed (where you cannot trust your own certificates and need to have an official one), we stopped searching around for the solution and got an actual certificate. That prevented all these errors and also enables the usage of a hosted agent.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>VSTS Personal Access Token for an Agent: Revoke after use</title>
			<link href="https://devopsjournal.io/blog/2018/08/03/VSTS-Personal-Access-Token-Agent-revoke-after-use"/>
			<updated>2018-08-03T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2018/08/03/VSTS-Personal-Access-Token-Agent-revoke-after-use</id>
			<content type="html">&lt;p&gt;Today I was listening to &lt;a href=&quot;http://www.radiotfs.com/Show/163/DevOpsDevOpswithWouterdeKortandHenryBeen&quot;&gt;RadioTFS episode 163&lt;/a&gt; on my commute, with guests &lt;a href=&quot;https://twitter.com/wouterdekort&quot;&gt;Wouter de Kort&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/henry_been/&quot;&gt;Henry Been&lt;/a&gt;. During the show Wouter mentioned that he always revoked his &lt;a href=&quot;https://roadtoalm.com/2015/07/22/using-personal-access-tokens-to-access-visual-studio-online/&quot;&gt;VSTS Personal Access Token&lt;/a&gt; after using it, especially when used for a Build Agent.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2018/20180803/2018_08_03_VSTS.png&quot; alt=&quot;VSTS Logo, no called Azure DevOps&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Apparently the PAT is only used for the initial authentication to VSTS/TFS and after that it isn’t needed anymore! That indeed means that you can revoke the token after it has been used and that you don’t need to keep the token around. Until this day, I had always copied the token into &lt;a href=&quot;https://keepass.info/&quot;&gt;keepass&lt;/a&gt; for keepsake, but I don’t need to do that anymore. This will also save me from seeing the expiration date and wondering if I need to update this token!&lt;/p&gt;

&lt;p&gt;If I ever need to register another agent, I can always create a new one. Revoking the old one also means that it cannot be used anymore, so that’s also less of a security concern then :-).&lt;/p&gt;

&lt;p&gt;Off course I had to test it first to see if it actually works, but it did :-). So wanted to save this for later in this post.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2018/20180803/2018_08_03_PAT.png&quot; alt=&quot;Screenshot of how to revoke the token&quot; /&gt;&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Chocolatey Server in Azure</title>
			<link href="https://devopsjournal.io/blog/2018/07/20/chocolatey-server-azure"/>
			<updated>2018-07-20T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2018/07/20/chocolatey-server-azure</id>
			<content type="html">&lt;p&gt;Recently I wanted to demo an example of how you can rollout &lt;a href=&quot;https://chocolatey.org/&quot;&gt;Chocolatey&lt;/a&gt; packages via your own choco server. Sometimes we cannot save every binary in VSTS to use it in a pipeline as an artifact and therefor I needed a different artifact server. Chocolatey provides a NuGet wrapper around binaries that you can easily track different versions with.&lt;/p&gt;

&lt;p&gt;Since that worked out an I now have a local document with the necessary steps to do so, I wanted to share that for later reuse.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2018/20180720/chocolatey.png&quot; alt=&quot;chocolatey&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Of course, Microsoft just announced that they started working on a different artifact server in VSTS (called &lt;a href=&quot;https://blogs.msdn.microsoft.com/devops/2018/07/09/universal-packages-bring-large-generic-artifact-management-to-vsts/?WT.mc_id=AZ-MVP-5003719&quot;&gt;Universal Package Management&lt;/a&gt;), next to the already available NuGet / npm / Maven &lt;a href=&quot;https://visualstudio.microsoft.com/team-services/package-management/?WT.mc_id=AZ-MVP-5003719&quot;&gt;package management&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Since I needed something to show, I started researching how you can do this with your own Chocoserver. Unfortunately, the installation for Choco.Server on Windows consists of &lt;a href=&quot;https://chocolatey.org/docs/how-to-set-up-chocolatey-server#setup-normally&quot;&gt;multiple steps&lt;/a&gt; that can take up some time. That just doesn’t feel right, so I wanted to see if I could wrap that inside of a PowerShell script being triggered from an &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-authoring-templates?WT.mc_id=AZ-MVP-5003719&quot;&gt;ARM template&lt;/a&gt; and Azure Automation DSC. I couldn’t seem to find the time to really start on it, but fortunately enough, my colleague &lt;a href=&quot;http://rvanmaanen.github.io&quot;&gt;Reinier van Maanen&lt;/a&gt; needed a bit of a challenge and picked this up. You can find his ARM template &lt;a href=&quot;https://github.com/rvanmaanen/arm.chocolateyserver&quot;&gt;here&lt;/a&gt;, together with the necessary PowerShell script.&lt;/p&gt;

&lt;p&gt;Beneath you can find the steps to get a working Choco.Server and Client up and running with a first basic package.&lt;/p&gt;

&lt;h1 id=&quot;install-a-chocoserver&quot;&gt;Install a Choco.Server&lt;/h1&gt;
&lt;p&gt;First, you’ll need a server to host the packages. You can host that with a couple of steps thanks to &lt;a href=&quot;https://twitter.com/MaanenReinier&quot;&gt;Reinier&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Git clone https://github.com/rvanmaanen/arm.chocolateyserver.git
cd arm.chocolateyserver
Connect-AzureRmAccount
Open parameters.json 
 - change dnsNameForPublicIP (max. 15 characters!)
 - change allowRdpFromThisIpAddress
.\Deploy-AzureResourceGroup.ps1 -ResourceGroupLocation &quot;West Europe&quot; -StorageAccountName &quot;ChocoARM&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;del&gt;Do note that the ARM template will report failure on the DSC step. To still get a working server you’ll need to log in to the new VM and execute the last 5 lines by hand.&lt;/del&gt; Update: thanks to &lt;a href=&quot;https://twitter.com/MaanenReinier&quot;&gt;Reinier&lt;/a&gt; this is now fixed! The ARM template now uses 2 steps to make sure the IIS management cmdlets are available for the second step. You can read how he fixed that &lt;a href=&quot;https://r-vm.com/depend-on-multiple-arm-script-extensions&quot;&gt;here&lt;/a&gt;. 
&lt;!-- markdown-link-check-disable --&gt;
To check it, you can navigate to &lt;a href=&quot;http://localhost/&quot;&gt;http://localhost/&lt;/a&gt;. 
&lt;!-- markdown-link-check-enable --&gt;
We are aware of the use of http, we need still to add that step to the script. We decided we could live with it for a small demo and blocking it from your own IP-address.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2018/20180720/2018_07_20_Choco_Server_packagelistexample.png&quot; alt=&quot;Chocopackagelisting&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;create-package-on-different-machine&quot;&gt;Create package on different machine&lt;/h1&gt;
&lt;p&gt;Install choco (elevated PowerShell) with this command:&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;Set-ExecutionPolicy&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Bypass&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Scope&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Process&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Force&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;iex&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;New-Object&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;System.Net.WebClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;DownloadString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;https://chocolatey.org/install.ps1&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;You are now ready to create new packages and push it to the Choco.Server. I did this on my own laptop, so therefor I am using the public IP address of the Azure server.
To wrap the files Chocolatey uses the well known &lt;a href=&quot;https://docs.microsoft.com/en-us/nuget/reference/nuspec?WT.mc_id=DOP-MVP-5003719&quot;&gt;NuSpec&lt;/a&gt; file, known from its use in &lt;a href=&quot;https://www.nuget.org/&quot;&gt;NuGet&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;choco new testpackage creates the default nuspec in that folder
change it, add notepad.exe into the \tools directory
remove ps1 files from the tools directory
choco pack on the folder containing the nuspec file.
choco push testpackage.{your.version.number.here}.nupkg --source http://{public.ip.of.chocoserver}/chocolatey --api-key={please update your key in the web.config} -force
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Note: the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-force&lt;/code&gt; is necessary because we are using http instead of https.&lt;/p&gt;

&lt;h1 id=&quot;client-machine-in-the-same-resourcegroupnsgnetwork&quot;&gt;Client machine (in the same resourcegroup/nsg/network!)&lt;/h1&gt;
&lt;p&gt;For demo purposes I created another Azure Windows Server in the same resource group, connected to the same network so the servers could easily connect to each other.&lt;br /&gt;
&lt;!-- markdown-link-check-disable --&gt;
On that extra server, verify that it can see the Choco.Server by navigating to &lt;a href=&quot;http://local.ip.of.chocoserver/chocolatey&quot;&gt;http://local.ip.of.chocoserver/chocolatey&lt;/a&gt; to see if you get a valid result.&lt;br /&gt;
Then you can install the chocolatey client, link you own server as a source and use that server to install your own package.&lt;/p&gt;

&lt;p&gt;Steps:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Install choco with the PowerShell command (see &apos;create package&apos;)
choco source add --name=internal_machine --source=http://local.ip.of.chocoserver/chocolatey
disable Internet Explorer Enhanced Security Configuration (IE ESC) to prevent the http page from loading (you are on a server). This step should be simpler when you are using https (as we should).
choco install testpackage
Files are copied into C:\ProgramData\chocolatey\bin\ by default
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;!-- markdown-link-check-enable --&gt;

&lt;p&gt;All in all, I found the process pretty straight forward. Now to start using the Choco.Server and push new versions to it!&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>GDBC DevOps on the Leaderboard</title>
			<link href="https://devopsjournal.io/blog/2018/06/16/GDBC-DevOps-on-the-Leaderboard"/>
			<updated>2018-06-16T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2018/06/16/GDBC-DevOps-on-the-Leaderboard</id>
			<content type="html">&lt;h2 id=&quot;global-devops-bootcamp&quot;&gt;Global DevOps Bootcamp&lt;/h2&gt;
&lt;p&gt;On the 16th of June 2018, &lt;a href=&quot;https://twitter.com/xpiritbv&quot;&gt;Xpirit&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/molausson&quot;&gt;Solidify&lt;/a&gt; organized a global event around the topic of DevOps and improving your release cadence. It is an ‘out of the box’ event with a lot of self organization where people around the global gathered on their free Saturdays to learn something new about DevOps.&lt;/p&gt;

&lt;p&gt;People interested in hosting a local venue went to the site
&lt;a href=&quot;https://www.globaldevopsbootcamp.com/&quot;&gt;https://globaldevopsbootcamp.com/&lt;/a&gt; and started from there. Anybody, anywhere could host an event. Eventually, 76 venues registered with 8.000 participants!&lt;/p&gt;

&lt;p&gt;The teams from Xpirit and Solidify provided completely configured &lt;a href=&quot;https://twitter.com/VSTS&quot;&gt;VSTS&lt;/a&gt; accounts, with challenges, webhooks, users and a filled git repository:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2018/20180816/2018_06_16_GDBC_Challenges.png&quot; alt=&quot;Challenges&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;showing-how-we-worked&quot;&gt;Showing how we worked&lt;/h2&gt;
&lt;p&gt;We got several questions during the event how we organized the leaderboard application and some participants where astonished we used the same tools for this as they had been working on today!
That’s why I wanted to share some of the stuff we did and what happened during the day!&lt;/p&gt;

&lt;h2 id=&quot;getting-points-for-work-items&quot;&gt;Getting points for work items&lt;/h2&gt;
&lt;p&gt;Every time a workitem’s state changed, a preconfigured webhook was triggered. When the team moved a work item to state ‘done’, they would get points for that work item. Points were depending on the amount of work necessary to complete the challenge.
The team could also request help, by adding a tag to the work item. Doing so would cost them half of the points for that work item, but also provided a link to a zipfile containing step by step instructions.&lt;/p&gt;

&lt;h2 id=&quot;leaderboard-application&quot;&gt;Leaderboard application&lt;/h2&gt;
&lt;p&gt;To organize all this, &lt;a href=&quot;https://twitter.com/pgroene&quot;&gt;Peter Groenewegen&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/GeertvdC&quot;&gt;Geert van de Cruijsen&lt;/a&gt; created a leaderboard application for last years event. You can find the source for it on GitHub:
&lt;a href=&quot;https://github.com/XpiritBV/LeaderboardsGlobalDevopsBootcamp&quot;&gt;github.com/xpiritbv&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We build on that for this years version, where Peter has added the webhook callback so VSTS could tell us when a workitem changed.&lt;/p&gt;

&lt;p&gt;Off course, this .NET core application is hosted in Azure on an App Service instance backed by an Azure SQL Database. The code is on GitHub and we created a build and release pipeline in VSTS:
&lt;img src=&quot;/images/2018/20180816/2018_06_16_GDBC_Build.png&quot; alt=&quot;Build&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This build would trigger when a pull request got merged into master and after successfully running all unit tests would trigger a release.
&lt;img src=&quot;/images/2018/20180816/2018_06_16_GDBC_Release.png&quot; alt=&quot;Build&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;devops-for-the-leaderboard-application&quot;&gt;DevOps for the leaderboard application&lt;/h1&gt;
&lt;p&gt;To check the application during the event, we created a dashboard to monitor the performance of the application and the database.
&lt;img src=&quot;/images/2018/20180816/2018_06_16_GDBC_Dashboard.png&quot; alt=&quot;Dashboard&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;during-the-event&quot;&gt;During the event&lt;/h2&gt;
&lt;p&gt;The event started everywhere at 10:00 AM local time, so New Zealand and Australia got to be the first to use the application. We were a sleep during most of that timeframe, but we checked during the start to see if there where no errors. Luckily, that wasn’t the case!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2018/20180816/2018_06_16_GDBC_By_Jesse_Houwing.jpg&quot; alt=&quot;DevOps with Geert van der Cruijsen by Jesse Houwing&quot; /&gt;
&lt;em&gt;Checking issues together with Geert, image by &lt;a href=&quot;https://twitter.com/jessehouwing/&quot;&gt;Jesse Houwing&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;emea-region-starting-with-the-challenges&quot;&gt;EMEA region starting with the challenges&lt;/h3&gt;
&lt;p&gt;When we started in Hilversum, The Netherlands, 10:00 AM CET, we noticed the average page load time climbing up and up. Apparently, a lot of venues in the EMEA region where using the leaderboard and where updating the workitems, causing some load on the webhook as well!&lt;/p&gt;

&lt;p&gt;We quickly scaled the App Service Plan and the Azure SQL Database to fix the page loads. This was important, because the webhooks where also on the same endpoint. When a webhook fails a couple of times, it will be disabled! That would mean teams not getting new points for the challenges they would complete!&lt;/p&gt;

&lt;p&gt;Thanks to the power of Azure and our team being enabled to fix things while running, we mitigated the issue.&lt;/p&gt;

&lt;h4 id=&quot;failing-webhooks&quot;&gt;Failing webhooks&lt;/h4&gt;
&lt;p&gt;A couple of hours later, someone spotted errors in Application Insights for the calls into the webhook. Checking the callstacks and exception messages, we found the culprit. We made sure we checked the workitems coming in to find their correct tags so we could find the points they where gathering, but we didn’t anticipated our participants creating their own workitems and tasks to distribute the work between them!
This meant the webhook was being called with workitems that didn’t have &lt;strong&gt;any&lt;/strong&gt; tags! So: a simple edge case we missed in our unit tests!&lt;/p&gt;

&lt;p&gt;A commit, push, pull request, review and merge later, the CI/CD pipeline we created kicked in and the application was pushed to production! Just like the teams where learning to use today!&lt;/p&gt;

&lt;h4 id=&quot;other-issues&quot;&gt;Other issues&lt;/h4&gt;
&lt;p&gt;Somehow, some teams managed to trigger the webhook in such a way that we got a duplicate record in the database. We found out about this, again through Application Insights, and fixed the issue quickly. How they managed to trigger this, is something we will look into before using the application again.&lt;/p&gt;

&lt;h2 id=&quot;can-you-spot-where-we-scaled-the-database&quot;&gt;Can you spot where we scaled the database?&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/2018/20180816/2018_06_16_GDBC_WebApp.png&quot; alt=&quot;WebApp&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;usage-throughout-the-day&quot;&gt;Usage throughout the day&lt;/h2&gt;
&lt;p&gt;There was a noticeable bump for the period EMEA region was live:
&lt;img src=&quot;/images/2018/20180816/2018_06_16_GDBC_Users.png&quot; alt=&quot;Users&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;closing&quot;&gt;Closing&lt;/h2&gt;
&lt;p&gt;All in all, I think we managed to keep the leaderboard and the webhooks in the air without our users noticing much of these issues. Great to see what a team can do when the have control over their entire pipeline, even into production!&lt;/p&gt;

&lt;p&gt;That’s why we need to keep repeating the message: empower your teams!&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>DevOps and Telemetry: Support on the supporting systems</title>
			<link href="https://devopsjournal.io/blog/2018/04/02/DevOps-and-Telemetry-support-the-support-systems"/>
			<updated>2018-04-02T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2018/04/02/DevOps-and-Telemetry-support-the-support-systems</id>
			<content type="html">&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; This is part 3 in a series of posts about DevOps and the role of telemetry in it. In part one I described the reasoning behind the series and explained how I started with logging (usage) telemetry for a SaaS application. You can read part 1 in the series &lt;a href=&quot;/blog/2018/03/14/DevOps-and-Telemetry-Insights-into-your-application&quot;&gt;here&lt;/a&gt;.
In this post I want to explain about the next step: logging information about the systems that support the application: servers, database, storage and anything that comes with that.&lt;/p&gt;

&lt;h2 id=&quot;series-overview&quot;&gt;Series overview&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2018/03/14/DevOps-and-Telemetry-Insights-into-your-application&quot;&gt;Part 1&lt;/a&gt; - My journey with telemetry and starting with logging&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2018/03/22/DevOps-and-Telemetry-Insights-supporting-systems&quot;&gt;Part 2&lt;/a&gt; - Supporting systems and how to gather that information (this post)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2018/04/02/DevOps-and-Telemetry-support-the-support-systems&quot;&gt;Part 3&lt;/a&gt; - Supporting the support systems&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;ssl-certificates---validity&quot;&gt;SSL certificates - validity&lt;/h2&gt;

&lt;h3 id=&quot;ssl-certificates-industry-standards-on-encryption&quot;&gt;SSL certificates: industry standards on encryption&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://www.ssllabs.com/&quot;&gt;SSLLabs&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;security-headers&quot;&gt;Security headers&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://securityheaders.com/&quot;&gt;https://securityheaders.com/&lt;/a&gt;&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>DevOps and Telemetry: Supporting systems</title>
			<link href="https://devopsjournal.io/blog/2018/03/22/DevOps-and-Telemetry-Insights-supporting-systems"/>
			<updated>2018-03-22T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2018/03/22/DevOps-and-Telemetry-Insights-supporting-systems</id>
			<content type="html">&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; This is part 2 in a series of posts about DevOps and the role of telemetry in it. In part one I described the reasoning behind the series and explained how I started with logging (usage) telemetry for a SaaS application. You can read part 1 in the series &lt;a href=&quot;/blog/2018/03/14/DevOps-and-Telemetry-Insights-into-your-application&quot;&gt;here&lt;/a&gt;.
In this post I want to explain about the next step: logging information about the systems that support the application: servers, database, storage and anything that comes with that.&lt;/p&gt;

&lt;h2 id=&quot;series-overview&quot;&gt;Series overview&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2018/03/14/DevOps-and-Telemetry-Insights-into-your-application&quot;&gt;Part 1&lt;/a&gt; - My journey with telemetry and starting with logging&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2018/03/22/DevOps-and-Telemetry-Insights-supporting-systems&quot;&gt;Part 2&lt;/a&gt; - Supporting systems and how to gather that information (this post)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2018/04/02/DevOps-and-Telemetry-support-the-support-systems&quot;&gt;Part 3&lt;/a&gt; - Supporting the support systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/images/2018/20180329/20180329_03_Dashboard.png&quot; alt=&quot;Dashboard example&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;telemetry-on-supporting-systems&quot;&gt;Telemetry on supporting systems:&lt;/h1&gt;

&lt;p&gt;For a SaaS application running on an &lt;a href=&quot;https://docs.microsoft.com/azure/app-service/azure-web-sites-web-hosting-plans-in-depth-overview?WT.mc_id=AZ-MVP-5003719&quot;&gt;Azure Web Service Plan&lt;/a&gt;, you can use a lot of components, so I’ll focus on stuff I have used in the past and are most commonly used:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;App Service Plan&lt;/li&gt;
  &lt;li&gt;SQL Database&lt;/li&gt;
  &lt;li&gt;Blob Storage&lt;br /&gt;
This should be enough to give a broad overview of the standard monitoring options.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;available-information-from-application-insights&quot;&gt;Available information from Application Insights&lt;/h2&gt;
&lt;p&gt;I’ll start at the point from the previous post where I added &lt;a href=&quot;https://docs.microsoft.com/azure/application-insights/app-insights-overview?WT.mc_id=AZ-MVP-5003719&quot;&gt;Application Insights&lt;/a&gt; to the application: I had (request) telemetry available from that, with visibility into all calls to depending services. From this, I started creating a dashboard to bring all this information together in one view, so that it could be shared with the team responsible for building and running the application. From Application Insights, I always add the application map to the main telemetry dashboard for the operations team. In that map, you’ll get a quick insight into ALL aspects of the application, including the parts that are suffering (performance wise).
&lt;img src=&quot;https://docs.microsoft.com/nl-nl/azure/application-insights/media/app-insights-app-map/02.png&quot; alt=&quot;Application Insights Map&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;app-service-plan&quot;&gt;App Service Plan&lt;/h2&gt;
&lt;p&gt;The default telemetry on an App Service Plan level are pretty basic. You can see CPU and Memory usage of the hosted web server, with data in and out. 
&lt;img src=&quot;/images/2018/20180329/20180329_04_AppServicPlan.png&quot; alt=&quot;App Service Plan&quot; /&gt;&lt;br /&gt;
I do check this ever so often, but not on a daily basis. If you have more than one app service on the same plan, then you can check to see if there is an application that is hogging resources. To do so, navigate to: App Service Plan –&amp;gt; Diagnose and solve problems –&amp;gt; 
Tools - Metrics per instance. &lt;em&gt;Note&lt;/em&gt; this menu item keeps moving around. I think this is the third location I’ve seen this item appear. Search around if you cannot find it.&lt;/p&gt;

&lt;h2 id=&quot;app-service&quot;&gt;App Service&lt;/h2&gt;
&lt;p&gt;On the App Service level itself, you get most of the same information as on a Plan level, but only for the application you are viewing. Additionally, you have information available like: number of requests, erroneous requests (can be filtered per status code (4xx and 5xx)) and CPU utilization of this app service. See the information about the metrics from Application Insights for more (and better in my opinion) informational data.&lt;/p&gt;

&lt;h2 id=&quot;database&quot;&gt;Database&lt;/h2&gt;
&lt;p&gt;One example of the additional telemetry data that I got from Application Insights was for the Azure SqL Database that was used. Thanks to Application Insights, you’ll get the following (very handy!) information:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Query duration&lt;/li&gt;
  &lt;li&gt;Number of times a query has been run against the database&lt;/li&gt;
  &lt;li&gt;Where that query has been called from (e.g. in your application code) 
Some of this information is also visible from the Azure SQL Database itself, but I think Application Insights provides nicer reporting on it.&lt;br /&gt;
This came in very handy when searching for performance issues throughout the application. I made this information available in a separate dashboard to be used for hunting down those issues, because I didn’t need them for the operational overview that I made sure to have always visible on a separate screen.&lt;br /&gt;
&lt;em&gt;Note:&lt;/em&gt; some of this information is also available from &lt;a href=&quot;https://docs.microsoft.com/azure/sql-database/sql-database-query-performance?WT.mc_id=AZ-MVP-5003719&quot;&gt;Query Insights&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/images/2018/20180329/20180329_QueryPerformanceInsights.png&quot; alt=&quot;Query Performance Insights&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;database-transaction-units-dtu&quot;&gt;Database Transaction Units (DTU)&lt;/h3&gt;
&lt;p&gt;Next up is the standard database telemetry that Azure already logged for us: the most interesting parts are database size, maximum database size and even more important: DTU measurements! For a more in dept explanation about DTU’s, check Microsoft’s explanation &lt;a href=&quot;https://docs.microsoft.com/azure/sql-database/sql-database-what-is-a-dtu?WT.mc_id=AZ-MVP-5003719&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Picking a good service tier for your database can be tricky and depends heavily on you usage patterns. If you have spikey traffic (think long running transactions or daily ETL tasks), you can consider changing the database tier up front or over provisioning (can be costly). Of course, you can think of other tactics to prevent running into the Max DTU for your service level, like caching (always tricky!), offloading heavy writes &amp;amp; updates to a different storage mechanism (or sticking it in a queue!).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2018/20180329/20180329_02_DTU.png&quot; alt=&quot;DTU&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Anyway: make sure to test with a representative load before running it in production! Seeing a chart link above is not great in production.&lt;br /&gt;
Changing the service tier is possible, but you really do not want to do this during a long running transaction. This will only make your performance issue last longer :-).&lt;/p&gt;

&lt;h2 id=&quot;blob-storage&quot;&gt;Blob Storage&lt;/h2&gt;
&lt;p&gt;On blob storage you’ll want to start with monitoring these metrics: Ingress (traffic &lt;strong&gt;into&lt;/strong&gt; the storage account, so uploads), Egress (traffic &lt;strong&gt;out of the&lt;/strong&gt; storage account, so downloads). Next up will be the latency and number of requests.&lt;br /&gt;
Depending on the layout of your storage account, you might want to dig deeper in the used ‘folders’ or ‘containers’ inside of the storage account. I had once a multitenant SaaS application that billed the tenants for (parts of) the used storage, so I talked to the storage API to get file and size measurements per tenant. To start retrieving that information, you can look &lt;a href=&quot;https://docs.microsoft.com/rest/api/storageservices/blob-service-rest-api?WT.mc_id=AZ-MVP-5003719&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;next-up&quot;&gt;Next up:&lt;/h2&gt;
&lt;p&gt;The next post in this series will be: ‘Part 3 - Supporting of the support systems’. I think it could be helpful to have a (start of) a list of items I started tracking that come up when checking the support systems like blob storage and SSL certificates. Sometimes these parts of the system are forgotten, until it is to late! I’ll dive into that in the next post.&lt;/p&gt;

&lt;p&gt;I’ll update this post with a link when Part 3 is available.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>DevOps and Telemetry: Insights into your application</title>
			<link href="https://devopsjournal.io/blog/2018/02/23/DevOps-and-Telemetry-Insights-into-your-application"/>
			<updated>2018-02-23T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2018/02/23/DevOps-and-Telemetry-Insights-into-your-application</id>
			<content type="html">&lt;p&gt;I like to make work and performance visible for teams by giving them the necessary information to gauge the performance of a system, both on a daily basis as well as over time. This will help them to find less performant parts of the system, or checking engagement statistics. Sometimes I feel this part of the process is forgotten when talking about DevOps these days. People will focus on bringing the different teams together, create vertically oriented teams and changing the static mindset into ‘the DevOps mindset’.&lt;/p&gt;

&lt;p&gt;In my opinion, telemetry Is a very important part into the DevOps journey. Without it, it’s hard to prove anything: whether it is the effectiveness of your organization into rolling faster, or checking usage statistics on new features. Step one should be to get a solid understanding of (a lot of different aspects of) your application.&lt;/p&gt;

&lt;p&gt;This series of posts will go through my own journey in this aspect of DevOps.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2018/20180314/20180314_02.jpeg&quot; alt=&quot;insights&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When I was the team lead for a multi-tenant SaaS product we hosted for our customers, I made sure we enabled our product owner, management &lt;strong&gt;and the dev team&lt;/strong&gt; to get important insights in the availability, performance and usage of our systems.&lt;br /&gt;
As we followed the adagio: ‘You build it, you run it’, I made sure it was possible to view the current (near real-time) performance on a dashboard and more business information on a daily basis, like the number of (new/recurring) users inside a Business Intelligence product. We even tracked supporting systems to make sure everything was up to our standards (think e.g. of SSL certificates via &lt;a href=&quot;https://www.ssllabs.com/&quot;&gt;SSLLabs&lt;/a&gt; for validity and key strength), SAS Key validity and more.&lt;/p&gt;

&lt;h2 id=&quot;series&quot;&gt;Series&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2018/03/14/DevOps-and-Telemetry-Insights-into-your-application&quot;&gt;Part 1&lt;/a&gt; - My journey with telemetry and starting with logging&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2018/03/29/DevOps-and-Telemetry-Insights-supporting-systems&quot;&gt;Part 2&lt;/a&gt; - Supporting systems and how to gather that information (this post)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2018/04/02/DevOps-and-Telemetry-support-the-support-systems&quot;&gt;Part 3&lt;/a&gt; - Supporting the support systems&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;part-1-in-the-series&quot;&gt;Part 1 in the series&lt;/h3&gt;
&lt;p&gt;In this first post I’m exploring the information I found necessary to have and will follow this up in another post with several methods to gather that information. The closing part of this series will be an overview of tools that could be use to gather the necessary information and display inside of a dashboard.&lt;/p&gt;

&lt;h3 id=&quot;system&quot;&gt;System&lt;/h3&gt;
&lt;p&gt;I’ll base all examples in this series on a multi-tenant SaaS application, which was build with ASP.NET MVC. We hosted that application in Azure on an App Service Plan, making use of SQL Database, blob storage (blob/table and queues), key vault and more.&lt;/p&gt;

&lt;h1 id=&quot;part-1&quot;&gt;Part 1&lt;/h1&gt;
&lt;p&gt;What information do you need to check?&lt;/p&gt;

&lt;h2 id=&quot;are-we-up&quot;&gt;Are we up?&lt;/h2&gt;
&lt;p&gt;Maybe the first information we started gathering was information about the incoming requests to the web application: raw number of requests and request errors, together with information like server side duration, user agent, request path, tenantid and userid.&lt;br /&gt;
&lt;img src=&quot;/images/2018/20180314/20180314_health.png&quot; alt=&quot;health&quot; /&gt;
At first we gathered this information by just logging it into the database after a request was ended, but after a while we learned that this was putting quite a load on our database, locking tables on inserts and live checking them in production is not a great idea: performance will tank if you have a small database tier and &amp;gt; 800k entries in the table per month. We could start to shard that table, but even then it would still be a performance issue with our growing customer and user base. Remember, inserting new records in that table will lock it and new insert will have to wait.&lt;/p&gt;

&lt;p&gt;We decided to test and later implemented logging that same information into &lt;a href=&quot;https://azure.microsoft.com/en-us/services/storage/tables/?WT.mc_id=AZ-MVP-5003719&quot;&gt;table storage&lt;/a&gt;, with a sensible keying strategy that would effectively shard that information inside the table storage. That way, inserting and reading that data would no longer hit our database performance.&lt;/p&gt;

&lt;p&gt;This information could be loaded into our reporting engine without drawing any performance on our database, which was a big plus for us. Since loading the data from table storage was also fast, we could load it on demand to generate new reports &amp;amp; check performance issues on the fly (e.g. to see if it was an issue for one user, one tenant or all users).&lt;/p&gt;

&lt;h3 id=&quot;issue-with-this-method&quot;&gt;Issue with this method&lt;/h3&gt;
&lt;p&gt;Regularly loading and testing this information proved us that the system was running in a normal way, but we only saw that the application wasn’t available to the end user if there was limited or no data available: it will then take a while to see what the cause is and perhaps you didn’t even notice that the information was lacking if you aren’t checking for the volume of new data. You might even NOT notice anything, because you might think that there are less or no active users using the application!&lt;/p&gt;

&lt;h3 id=&quot;next-step---rolling-our-own-solution&quot;&gt;Next step - rolling our own solution&lt;/h3&gt;
&lt;p&gt;We then started with the search for a monitoring platform to test the availability of the application by testing the login page. Since the login page (usually) doesn’t actually hit the database, we even implemented a specific endpoint in the application that would check to see if the database was available and up to date with the latest schema. Of course, all other elements of the application (blob storage with its own parts, key vault, etc.) would also have to be checked, which could lead us into creating a performance bottleneck in our own system if we’d hit that endpoint a lot. Nevertheless, because we already had  such a monitoring solution in place, we added a web job to it that we could configure with url’s to check for at least a HTTP OK (200) result on that endpoint. When a couple of checks would fail, the system would trigger an email to the admins, telling them to start their analysis on the system. We figured that having a solution in place would be better than none. In the mean time, we’d search for a better way to monitor the system.&lt;/p&gt;

&lt;h3 id=&quot;a-better-way-for-us&quot;&gt;A better way (for us)&lt;/h3&gt;
&lt;p&gt;Since rolling your own solution can take quite some time to get things right, we also looked into other available ways to get the necessary insights into our application. Since we where running on Azure, and even with ASP.NET, implementing &lt;a href=&quot;https://azure.microsoft.com/en-us/services/application-insights/?WT.mc_id=AZ-MVP-5003719&quot;&gt;Application Insights&lt;/a&gt; was a low friction step. These days, you can start by adding Application Insights as an &lt;a href=&quot;https://azure.microsoft.com/en-us/blog/azure-web-sites-extensions/?WT.mc_id=AZ-MVP-5003719&quot;&gt;extension&lt;/a&gt; to your web app, so without even changing the code, but back than we had to just implement the NuGet package and add our telemetry key to our configuration. Adding two or three lines of code in both ASP.NET and a central JavaScript location and we where up and running.&lt;/p&gt;

&lt;p&gt;After the next rollout, we were gathering the (basic) data so that Application Insights could give us:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Easy and high level tracking of the performance of our application.&lt;/li&gt;
  &lt;li&gt;Finding errors in both our own requests, but also to our dependencies like SQL server, blob storage, key vault, etc.&lt;/li&gt;
  &lt;li&gt;A very easy way to initiate alerts to our admins: even adding sending an email to a distribution group is a simple way to get started.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/images/2018/20180314/20180314_03.png&quot; alt=&quot;insights&quot; /&gt;&lt;/p&gt;

&lt;p&gt;With this information available, we created dashboards inside the Azure portal that we’d share between the team members that had access to the Azure subscription.&lt;/p&gt;

&lt;p&gt;Since we didn’t add any additional data into Application Insights like tenantId or userId for better correlation to those levels, we needed to rely on Application Insights way of calculation user and session counts. Since these are gathered based on application insights own methods, we saw a difference with our own unique user counts. Application Insights would see a user logging into a different browser or device as a new user/session and count it thusly. Without a userId, it couldn’t relate the information back to the same user (of course!).&lt;/p&gt;

&lt;h3 id=&quot;next-step-log-additional-information&quot;&gt;Next step: log additional information&lt;/h3&gt;
&lt;p&gt;The next step would be to add that missing information into Application Insights, as well as any specific activities we would like to track. You can think about actions like adding/deleting a product to a basket, (not) paying for the items, etc. These items can then be used inside reporting.
Information about adding this information can be found &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/application-insights/app-insights-api-custom-events-metrics?WT.mc_id=AZ-MVP-5003719&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;next-step-long-term-data-retention&quot;&gt;Next step: long term data retention&lt;/h3&gt;
&lt;p&gt;After getting some trust in the information (checking it with our own logs), we can then start using the information of Application Insights into our Business Intelligence platform (just pick one that’s available to you and you can actually work in) and provide information about our application over a longer period of time. The Application Insights dashboards inside of the Azure portal would usually be shown for the last hour and the last week. If you want to track usage over a longer period of time, you’ll need to export it and use it in another place. One way to do so is exporting the logs into a SQL database and then consuming that data into a dashboarding solution of your choosing.&lt;/p&gt;

&lt;p&gt;A way to do so into Power BI is described &lt;a href=&quot;https://www.patrickvankleef.com/2017/12/04/use-power-bi-to-build-interactive-visualizations-based-on-application-insights-telemetry-data/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;next-up&quot;&gt;Next up:&lt;/h2&gt;
&lt;p&gt;The next post in this series will be: ‘Part 2 - Supporting systems and how to gather that information’. I think it could be helpful to have a (start of) a list of items I started tracking about supporting systems. What parts of your infrastructure does an ‘Ops’ team really care about? I’ll dive into that in the next post, which you can find &lt;a href=&quot;/blog/2018/03/29/DevOps-and-Telemetry-Insights-supporting-systems&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>VSTS Bulk Change WorkItemType</title>
			<link href="https://devopsjournal.io/blog/2018/02/23/vsts-workitemtype-change"/>
			<updated>2018-02-23T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2018/02/23/vsts-workitemtype-change</id>
			<content type="html">&lt;h2 id=&quot;update-process-templates&quot;&gt;Update process templates&lt;/h2&gt;
&lt;p&gt;Recently I had a customer request to update their process definition in Visual Studio Team Services (VSTS). They had 30+ different processes migrated from TFS (Team Foundation Server), so they were all Hosted XML processes.&lt;/p&gt;

&lt;p&gt;Somehow they had the process setup like this:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Epic –&amp;gt; Product Backlog Item&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which they requested me to convert to this:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Epic –&amp;gt; Feature –&amp;gt; Product Backlog Item&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In TFS you would grab &lt;a href=&quot;https://docs.microsoft.com/en-us/vsts/work/customize/reference/witadmin/witadmin-customize-and-manage-objects-for-tracking-work?WT.mc_id=DOP-MVP-5003719&quot;&gt;witadmin&lt;/a&gt; and change the process in a pretty straigthforward manner (once you’ve figured out all places you need to update!).
Unfortunately, you cannot run the change commands in witadmin against VSTS, only the list and export commands will work: see &lt;a href=&quot;https://docs.microsoft.com/en-us/vsts/work/customize/reference/witadmin/witadmin-import-export-categories?WT.mc_id=DOP-MVP-5003719&quot;&gt;here&lt;/a&gt;. Microsoft is working on a REST api to perform administrative tasks against VSTS. Luckily for me, they have also wrapped the REST calls in a nice C# NuGet package (&lt;a href=&quot;https://www.nuget.org/packages/Microsoft.TeamFoundationServer.Client/&quot;&gt;link&lt;/a&gt;)!&lt;/p&gt;

&lt;p&gt;Making changes to the process template isn’t available (yet?), although there is a strange method available named ‘UpdateWorkItemTypeDefinitionAsync’ in the ‘WorkItemTrackingClient’. The only info I can find about this is &lt;a href=&quot;https://en.wikipedia.org/wiki/Wikipedia:Link_rot&quot;&gt;here&lt;/a&gt;, which seems to indicate that you can only update (maybe add) a specific Work Item Type.   Since I also needed to update the tree structure in the ProcessConfiguration.xml file, I still need to export the process, make the necessary changes in the xml files, zip it back up and upload the file back into VSTS.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2018/20180223/20180226_01.png&quot; alt=&quot;VSTS screenshot&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;updating-work-items-to-the-new-type&quot;&gt;Updating work items to the new type&lt;/h2&gt;
&lt;p&gt;After doing so, the request was to convert &lt;strong&gt;all&lt;/strong&gt; the old Epics to the new Features. Off course, you can do this by using a query on the Epic work item type and using the UI to change them to Features, but this would take a lot of manual actions to do.&lt;/p&gt;

&lt;p&gt;Luckily  you can do this with the &lt;a href=&quot;https://www.nuget.org/packages/Microsoft.TeamFoundationServer.Client/&quot;&gt;TeamFoundationServer.Client&lt;/a&gt; NuGet package! When you start looking into this, I can highly recommend using Microsoft’s GitHub repo containing a lot of samples &lt;a href=&quot;https://github.com/Microsoft/vsts-dotnet-samples&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It took me some figuring out to get a good workflow in an application, so I have made the tools source available on my &lt;a href=&quot;https://github.com/rajbos/VSTSClient&quot;&gt;GitHub account&lt;/a&gt;.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Using excerpts in Jekyll</title>
			<link href="https://devopsjournal.io/blog/2017/12/29/jekyll-excerpts"/>
			<updated>2017-12-29T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2017/12/29/jekyll-excerpts</id>
			<content type="html">&lt;p&gt;I wanted to include at least some more information in the index page of my blog instead of just the publish date and title, so I searched around for some help to include an excerpt in Jekyll and found some help on &lt;a href=&quot;http://frontendcollisionblog.com/jekyll/snippet/2015/03/23/how-to-show-a-summary-of-your-post-with-jekyll.html&quot;&gt; this&lt;/a&gt; blog.&lt;/p&gt;

&lt;p&gt;The solution was very straightforward, but I’ll include it here for future reference.&lt;/p&gt;

&lt;h4 id=&quot;index-page&quot;&gt;Index page&lt;/h4&gt;
&lt;p&gt;In the index page, you can search the content of a post, check for specific tags and use the text between them:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;&amp;lt;!-- index.html --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;p&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;post-excerpt&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;


	


&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Note: if the specified tags aren’t found in the content, the first &lt;strong&gt;20&lt;/strong&gt; words will be used.&lt;/p&gt;
&lt;h4 id=&quot;posts&quot;&gt;Posts&lt;/h4&gt;
&lt;p&gt;In a post, you can now include the excerpt tags to add a specific excerpt:&lt;/p&gt;
&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;&amp;lt;!-- _posts/some-random-post.html --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
Here&apos;s all my content, and &lt;span class=&quot;c&quot;&gt;&amp;lt;!--excerpt.start--&amp;gt;&lt;/span&gt;here&apos;s where I want my summary to begin, and this is where I want it to end&lt;span class=&quot;c&quot;&gt;&amp;lt;!--excerpt.end--&amp;gt;&lt;/span&gt;.
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
		</entry>
	
		<entry>
			<title>Trying out Jekyll</title>
			<link href="https://devopsjournal.io/blog/2017/12/17/trying-out-jekyll-on-github-pages"/>
			<updated>2017-12-17T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2017/12/17/trying-out-jekyll-on-github-pages</id>
			<content type="html">&lt;p&gt;Trying out Jekyll on top of GitHub pages as a new blogging platform.
For know, I just needed a simple way to create posts, but add some stuff I am 
missing on my current method (&lt;a href=&quot;https://withknown.com/&quot;&gt;WithKnown&lt;/a&gt;), like RSS and Google Analytics.&lt;/p&gt;

&lt;p&gt;So far I like the easy setup (like: no installation whatsoever!) and the fact that it uses Jekyll to generate static pages.&lt;/p&gt;

&lt;p&gt;I started with the excelent guide of &lt;a href=&quot;http://jmcglone.com/guides/github-pages/&quot;&gt;Jonathan MCGlone&lt;/a&gt;.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Azure App Service - a quick way to take your app Offline</title>
			<link href="https://devopsjournal.io/blog/2017/11/19/take-an-azure-app-service-offline"/>
			<updated>2017-11-19T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2017/11/19/take-an-azure-app-service-offline</id>
			<content type="html">&lt;p&gt;After searching for the third time on how to do this, I thought it would be time to write about this here 😬.&lt;/p&gt;

&lt;p&gt;If you have an Azure App Service that for some reason should just display a message to the user, indicating that it isn’t available, you can do this.&lt;/p&gt;

&lt;p&gt;I have had several reasons to do this:&lt;/p&gt;

&lt;p&gt;single app service host, without a deployment slot and a big db update ( &amp;gt; 10 minutes
db hitting a spending limit and no wish to update the limit
moving dns names and certs between app service plans (were recreated with a better name)&lt;/p&gt;
&lt;h3 id=&quot;app_offlinehtm&quot;&gt;App_offline.htm&lt;/h3&gt;
&lt;p&gt;Create or place a new file in your webroot (wwwroot) named “app_offline.htm”. I usually create it using kudu. 
The appservice will see this and online serve this file, as long as it’s there. 
Note: the contents of the file can be anything, including css and images from anywhere else in your wwwroot.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>DotNetCore: Adding HTTPS to your MVC webapp</title>
			<link href="https://devopsjournal.io/blog/2016/08/20/dotnetcore-adding-https-to-your-mvc-webapp"/>
			<updated>2016-08-20T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2016/08/20/dotnetcore-adding-https-to-your-mvc-webapp</id>
			<content type="html">&lt;p&gt;
I wanted to use https in my dotnetcore application (v. 1.0.0-rc2-final) and had to dig around the web quite a bit to find the most recent
and working method to accomplish this. Eventually a link in the MVC github site lead to an example how to fix this 
(&lt;a title=&quot;link&quot; href=&quot;https://github.com/Rinsen/HttpsProblemWithKestrel&quot; target=&quot;_blank&quot;&gt;link&lt;/a&gt;).
&lt;/p&gt;
&lt;p&gt;
First, the most easy way I&apos;ve found to do this, is to add some custom middleware for redirecting all http requests to https:
&lt;/p&gt;
&lt;div class=&quot;language-c# highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HttpsRedirectMiddleware&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;readonly&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;RequestDelegate&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        
        &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;HttpsRedirectMiddleware&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RequestDelegate&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;_next&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Invoke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HttpContext&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IsHttps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nf&quot;&gt;HandleNonHttpsRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;_next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        
        &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;HandleNonHttpsRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HttpContext&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// only redirect for GET requests, otherwise the browser might not propagate the verb and request&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// body correctly.&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Equals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HttpContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;GET&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;StringComparison&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;OrdinalIgnoreCase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;StatusCode&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;403&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newUrl&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Concat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                    &lt;span class=&quot;s&quot;&gt;&quot;https://&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ToUriComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PathBase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ToUriComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ToUriComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;QueryString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ToUriComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Redirect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;newUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;permanent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;
  I&apos;ve added this right before the Mvc call:
&lt;/p&gt;

&lt;div class=&quot;language-c# highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AddMvc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
		</entry>
	
		<entry>
			<title>Links to Visual Studio Extensions</title>
			<link href="https://devopsjournal.io/blog/2016/06/04/links-to-visual-studio-extensions"/>
			<updated>2016-06-04T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2016/06/04/links-to-visual-studio-extensions</id>
			<content type="html">&lt;p&gt;Some links to important Visual Studio extensions for later reference:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Open Bin folder Visual Studio Extension: &lt;a href=&quot;https://visualstudiogallery.msdn.microsoft.com/d7c10a53-b3d9-4e8d-9538-88d452da6c07&quot;&gt;Visual Studio Gallery&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Wakatime: &lt;a href=&quot;https://wakatime.com/&quot;&gt;wakatime.com&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;SlowCheetah &lt;a href=&quot;https://visualstudiogallery.msdn.microsoft.com/69023d00-a4f9-4a34-a6cd-7e854ba318b5&quot;&gt;Visual Studio Gallery&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;SonarLint &lt;a href=&quot;http://www.sonarlint.org/visualstudio/&quot;&gt;sonarlint.org&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;T4MVC &lt;a href=&quot;http://bennor.github.io/AutoT4MVC/&quot;&gt;github.io&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
		</entry>
	
		<entry>
			<title>Not geting new windows 10 preview builds after reverting to an older build?</title>
			<link href="https://devopsjournal.io/blog/2016/03/03/not-getting-new-windows-10-preview-builds-after-reverting-back"/>
			<updated>2016-03-03T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2016/03/03/not-getting-new-windows-10-preview-builds-after-reverting-back</id>
			<content type="html">&lt;p&gt;If you previously had a new Windows 10 preview build installed in your computer and then you reverted back to an older build, you could lose access to the new build where the new build is no longer offered as an upgrade option. If you want to install that build again, deleting that build number from the list in the Registry Editor will restore the ability to upgrade.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsSelfHost\Applicability\RecoveredFrom 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;!-- markdown-link-check-disable --&gt;
&lt;p&gt;&lt;a href=&quot;https://www.askvg.com/fix-windows-10-insider-preview-build-10240-not-appearing-on-windows-update/&quot;&gt;Source&lt;/a&gt;
&lt;!-- markdown-link-check-enable --&gt;&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Integrate SonarQube with TFS 2015 update 1</title>
			<link href="https://devopsjournal.io/blog/2016/01/23/integrate-sonarqube-with-tfs-2015-update-1-build-tasks"/>
			<updated>2016-01-23T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2016/01/23/integrate-sonarqube-with-tfs-2015-update-1-build-tasks</id>
			<content type="html">&lt;p&gt;While migrating CI stuff from Jenkins into TFS 2015 SP1 I ran into &lt;a href=&quot;https://devblogs.microsoft.com/devops/build-tasks-for-sonarqube-analysis/?WT.mc_id=DOP-MVP-5003719&quot;&gt;this&lt;/a&gt; blog post from Microsoft explaining how to include &lt;a href=&quot;http://www.sonarqube.org/&quot;&gt;SonarQube&lt;/a&gt; runs information in the TFS Build Tasks. We have been running SonarQube on our projects for about a year now to gain some insights into Code Coverage and basic code smells. I sure don’t want to lose the information Sonar gives us.&lt;/p&gt;

&lt;p&gt;The problem was that we weren’t getting any results into SonarQube. The default way TFS runs the UnitTests is that it generates a trx file with coverage information in it. Unfortunately,when you have a VS Professional license, it will not generate the code coverage. You’d need a Enterprise license for it!&lt;/p&gt;

&lt;p&gt;In Jenkins, we used &lt;a href=&quot;https://github.com/OpenCover/opencover&quot;&gt;OpenCover&lt;/a&gt; to generate the coverage data, which integrates into the calls to the SonarQube runner. Its open source, which is a big plus.&lt;/p&gt;

&lt;h2 id=&quot;step-1&quot;&gt;Step 1:&lt;/h2&gt;
&lt;p&gt;Create a new location on the server to place the coverage reports into. I’ve used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;C:\OpenCover\&lt;/code&gt; for this, and let OpenCover generate a file for each assembly we’re testing, using the name of the assembly as the filename for the xml report.&lt;/p&gt;

&lt;h2 id=&quot;step-2&quot;&gt;Step 2:&lt;/h2&gt;
&lt;p&gt;Add arguments to the SonarQube build step to tell Sonar where to find the coverage report. This needs to be done in the start step:&lt;/p&gt;

&lt;p&gt;Add this argument to ‘Additional Settings’:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/d:sonar.cs.opencover.reportsPaths=&quot;C:\OpenCover\SolutionName.AssemblyName.UnitTests.opencover.xml&quot;: Sonar settings
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;/images/2016/20160123/20160123_01.png&quot; alt=&quot;TFS screenshot&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;step-3&quot;&gt;Step 3:&lt;/h2&gt;
&lt;p&gt;Add in an additional Command Line build task to start the OpenCover tools. As you can see, I’ve added this after the build step. I think Sonar needs the build step for some data, but I need to test if it still works when we move that step out of the Sonar Start and End steps. Running the OpenCover tool should be enough, but I’m not sure of that.&lt;/p&gt;

&lt;p&gt;Add these arguments:&lt;/p&gt;
&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
-output:&quot;C:\OpenCover\SolutionName.AssemblyName.opencover.xml&quot;

-register:user

-target:&quot;C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe&quot;

-targetargs:&quot; \&quot;{path_to_tfs_workfolder}\bin\Release\SolutionName.AssemblyName.UnitTests.dll&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;step-4&quot;&gt;Step 4:&lt;/h2&gt;
&lt;p&gt;Run the build and see some coverage results!&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Windows 10: Enable 8.1 fly-out style WiFi / VPN menu</title>
			<link href="https://devopsjournal.io/blog/2016/01/09/windows-10-enable-81-fly-out-style-wifi-vpn-menu"/>
			<updated>2016-01-09T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2016/01/09/windows-10-enable-81-fly-out-style-wifi-vpn-menu</id>
			<content type="html">&lt;p&gt;I’m not the only one who is annoyed by the new Windows 10 way to connect to VPN connections. The new route takes a lot of new clicks, just to connect to a VPN! The old Windows 8 style was a lot faster. Since I frequently change connections at work, It’s a recurring annoyance everyday :-(.&lt;/p&gt;

&lt;p&gt;Today, I’ve found out that there is a simple registry setting to revert the dialogs to the old Windows 8 style.&lt;/p&gt;

&lt;p&gt;Registry key:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Control Panel\Settings\Network\ReplaceVan
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Values:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;0 - Default
1 - Network settings in settings app
2 - Windows 8 style
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note: you’ll need admini&lt;/p&gt;

&lt;p&gt;strator rights to change the settings, or use a tool like &lt;a href=&quot;http://winaero.com/&quot;&gt;RegOwnershipEx&lt;/a&gt; to change the settings. Take ownership of the folder and then open the register editor to change the value.&lt;/p&gt;

&lt;p&gt;Thanks to &lt;a href=&quot;https://twitter.com/Nick_Craver/status/685593210186543107&quot;&gt;Nick Craver&lt;/a&gt;, for mentioning it was possible to change it.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>Visual Studio 2015 update 1 - Service update 1</title>
			<link href="https://devopsjournal.io/blog/2016/01/03/visual-studio-2015-update-1-service-update-1"/>
			<updated>2016-01-03T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2016/01/03/visual-studio-2015-update-1-service-update-1</id>
			<content type="html">&lt;p&gt;Just putting this out here for future reference: there is a service update for VS2015 update 1 to fix some issues. I needed this update to fix an error in VS with &lt;strong&gt;T4MVC&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Knowledge base article: &lt;a href=&quot;https://www.microsoft.com/en-us/download/confirmation.aspx?id=50376&amp;amp;&amp;amp;WT.mc_id=DOP-MVP-5003719&quot;&gt;Link&lt;/a&gt;&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>WakaTime</title>
			<link href="https://devopsjournal.io/blog/2015/05/19/wakatime.md"/>
			<updated>2015-05-19T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2015/05/19/wakatime.md</id>
			<content type="html">&lt;p&gt;&lt;a href=&quot;https://wakatime.com/dashboard&quot;&gt;https://wakatime.com/dashboard&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Plugin for Visual Studio (and other editors) to log hours spend in the editor. Free account only retains the information for a couple of weeks and gives you an overview of time per project/solution and per language. Really neat to see those stats.&lt;/p&gt;

&lt;p&gt;Currently I have this extension installed on both my laptop, pc and in a VM designated for SharePoint development.&lt;/p&gt;
</content>
		</entry>
	
		<entry>
			<title>ASP.NET Demoproject started</title>
			<link href="https://devopsjournal.io/blog/2015/05/19/aspnet-demoproject-started"/>
			<updated>2015-05-19T00:00:00+00:00</updated>
			<id>https://devopsjournal.io/blog/2015/05/19/aspnet-demoproject-started</id>
			<content type="html">&lt;p&gt;Tonight, I had the idea of starting a new project to test and demo new area’s and features of ASP.NET. Initially it will be a project to test ASP.NET MVC 6 from Visual Studio 2015. I’ll set the project on GitHub for future reference and to get more familiar with git.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href=&quot;https://github.com/rajbos/dotnetcore-webapp&quot;&gt;https://github.com/rajbos/dotnetcore-webapp&lt;/a&gt;.&lt;/p&gt;
</content>
		</entry>
	

</feed>
