{"version": "https://jsonfeed.org/version/1", "title": "KPLauritzen.dk", "home_page_url": "https://kplauritzen.dk/", "feed_url": "https://kplauritzen.dk/feed_json_created.json", "description": null, "icon": null, "authors": [], "language": "en", "items": [{"id": "https://kplauritzen.dk/2026-02-08-claude-code-links/", "url": "https://kplauritzen.dk/2026-02-08-claude-code-links/", "title": "Zvi Mowshowitz on Claude Code", "content_html": "<h1 id=\"zvi-mowshowitz-on-claude-code\">Zvi Mowshowitz on Claude Code</h1>\n<p>It is hard to go more than a few days without some major story or a new hype cycle on AI. \nRecently, a lot of people have discovered that Claude Code is quite good actually. </p>\n<p>A blog I really enjoy, <a href=\"https://thezvi.substack.com/\">\"Don't Worry About The Vase\" by Zvi Mowshowitz</a>, has extensive coverage of what is going on in the AI world. \nI'm always excited to have a look at his weekly AI updates on thursdays, even if I rarely finish the whole thing. He really is a prolific writer. See the latest one here: <a href=\"https://thezvi.substack.com/p/ai-154-claw-your-way-to-the-top\">AI #154: Claw Your Way To The Top</a>, focused on the recent OpenClaw and Moltbook hype (small aside, has that already died down again?)</p>\n<p>Recently, he has started posting about Claude Code, and about how people are using it or realizing that it can do a lot for them. I find it VERY interesting! Coding agents are really transforming the way I work, and I think they are going to be a huge part of the future of work in general.</p>\n<p>The posts primarily consists of snippets from Twitter with some commentary, but since I'm not on Twitter, this is a great way for me to get a sense of what people are saying about it.\nHere is the latest one: <a href=\"https://thezvi.substack.com/p/claude-code-4-from-the-before-times\">Claude Code #4: From The Before Times</a>.</p>\n<p>He pulls an interesting quote from Andrej Karpathy:</p>\n<blockquote>\n<p><a href=\"https://x.com/karpathy/status/2015887919924617657\">Andrej Karpathy</a>: Love the word \"comprehension debt\", haven't encountered it so far, it's very accurate. It's so very tempting to just move on when the LLM one-shotted something that seems to work ok.</p>\n</blockquote>\n<p>I was amused to try the new <code>/insights</code> command recently, mentioned here: </p>\n<blockquote>\n<p>The new Claude Code command /insights will read your last month\u2019s message history and give you suggestions to improve your workflow. </p>\n</blockquote>\n<p>There is a section of best practices from different sources: <a href=\"https://thezvi.substack.com/i/185474971/skilling-up\">Skilling Up</a></p>\n<p>You can also go have a look at some of the previous ones, which are quite interesting as well:\n- <a href=\"https://thezvi.substack.com/p/claude-codes\">Claude Codes</a>\n- <a href=\"https://thezvi.substack.com/p/claude-coworks\">Claude Coworks</a>\n- <a href=\"https://thezvi.substack.com/p/claude-codes-3\">Claude Codes #3</a></p>", "image": null, "date_published": "2026-02-08T00:00:00+00:00", "authors": [], "tags": null}, {"id": "https://kplauritzen.dk/2025-12-15-fred-stats-rant/", "url": "https://kplauritzen.dk/2025-12-15-fred-stats-rant/", "title": "Fred", "content_html": "<h1 id=\"fred\">Fred</h1>\n<p>My good friend, Fred, has started <a href=\"https://rubberbottom.substack.com\">blogging on substack</a> recently. </p>\n<p>Fred has MANY skills. He has been DOING things, been poking at the world with every stick he has ever found, changed careers to something more exciting more times than I can easily express.</p>\n<p>He could probably express it. One skill he has, maybe the thing he is the best at, is writing. </p>\n<p>Actually, there is at least one thing I'll rank higher than Fred's writing. That is a good Fred rant. And his blog feels like a series of particularly high-quality rants, interspersed with actual, honest-to-god, advice on being a good human. </p>\n<p>So I'll drop whatever else I'm doing whenever I see a [FRED] post in my <a href=\"../2025-11-25-reading-rss-feeds/\">RSS feed</a>, get comfy in my chair, and enjoy the fireworks. </p>\n<p>You might need an example:\nHow about <a href=\"https://substack.com/home/post/p-180691141\">this one</a>, opening on the toilet at the therapists office, setting the scene like this</p>\n<blockquote>\n<p>I\u2019ve just suffered through part bowel movement, part catholic exorcism\u2026</p>\n<p>Resulting in one of those near-mythological Fantasy Epic wipes that need more paper than all of Robert Jordan\u2019s Wheel Of Time series.</p>\n<p>This is a Sisyphean task straight out of Greek mythology - pushing a brown tar-like with inexplicable-grains-in-it boulder uphill every day until the world runs out of patience and paper.</p>\n</blockquote>\n<p>IS THAT A HOOK OR WHAT?</p>\n<p>Ok, so he is also incredibly crass. That just makes it better. </p>\n<h2 id=\"on-having-a-gooch-day\">On having a Gooch Day</h2>\n<p>His most <a href=\"https://rubberbottom.substack.com/p/why-most-days-are-unproductive\">recent post</a> is about productivity. </p>\n<p>I think you should read the post, but the shortest possible summary is:\n- Suppose your daily produtivity is following a truncated normal distribution, from 0% to 100% productive days. \n- Then you will have a few days in a month that feel very productive, a few that feel not-at-all productive, and most days will be in-between. Those are the Gooch Days.</p>\n<p>Fred points out that you can't rely on the super productive days, they are too rare. Instead, the real hero is the Gooch. This is where progress is made, because this is where the volume is at. </p>\n<p>I wanted to play around with this intuition a little bit, and since <a href=\"https://www.maragu.dev/blog/go-is-my-hammer-and-everything-is-a-nail\">Python is my hammer and everything is a nail</a>, I fired up some matplotlib. </p>\n<p>Given a normal distribution with a mean of 50% and a standard deviation of 20%, we can approximate the histogram Fred showed. And if we want to know how much each day contributes to the total productivity, we can plot $x \\times p(x)$, the expected contribution. For me, it helped to think of Expected Value calculations: Imagine you make a bet that will give 100 EUR if you win, and you have a 20% chance to win. The expected value is $100 \\times 0.2 = 20$ EUR.</p>\n<p>So now the height on the histogram is not representing the frequency of days with that amount of productivity. Instead it represents how much those days contribute to the overall productivity (of the month/year/whatever).</p>\n<p>I've plotted both the assumed productivy distribution and the contribution below. </p>\n<p><img alt=\"productivity distributions\" src=\"../images/productivity_contribution.png\" /></p>\n<p>Note the area with a slightly off-putting color, between 20% and 80% productivity, the Gooch Days. All together, they represent 87% of your total productivity! They are basically all that matter. </p>", "image": null, "date_published": "2025-12-15T00:00:00+00:00", "authors": [], "tags": null}, {"id": "https://kplauritzen.dk/2025-11-25-reading-rss-feeds/", "url": "https://kplauritzen.dk/2025-11-25-reading-rss-feeds/", "title": "Reading RSS feeds", "content_html": "<h1 id=\"reading-rss-feeds\">Reading RSS feeds</h1>\n<p>My good friend <a href=\"https://zeth.dk/\">Zeth</a> just posted on <a href=\"https://zeth.dk/rss-reader-and-news-habits/\">using RSS feeds to reduce distractions</a> and I think that is super interesting! He is always running small experiments on himself to optimize or nudge his behaviour in some way. Here, here is trying out RSS feeds. Go read his post now.</p>\n<h2 id=\"why-rss\">Why RSS</h2>\n<p>Not being easily distracted seems like a core skill today.\nI'm using RSS readers to reduce the slot machine behaviour from clicking on a bookmark hoping for a new exciting post.\nNow there is just one place to go, and I can more easily control my cravings.</p>\n<p>I'm also using RSS readers as a way of being less reliant on someone else's algorithm deciding what I get to see. Some RSS readers push AI filtering or \"trending topics\", as Zeth mentions. I don't like that!\nAvoiding the algorithm means that curation is only up to you.</p>\n<p>I'm using <a href=\"https://freshrss.org/index.html\">FreshRSS</a> self-hosted in a one-node Kubernetes cluster in my home lab.\nI like that it \"gets out of the way\" quickly.\nIt is just a chronological list of posts, with some simple options to see e.g. all posts from a certain feed or category.</p>\n<p>Full disclosure: I also still have a very bad habit of opening Hacker News whenever I'm stuck on something at work. Do as I say, not as I do-</p>\n<h2 id=\"good-rss-reading-habits\">Good RSS reading habits</h2>\n<p><strong>Find good feeds to subscribe to!</strong>\nBy far the most important thing, having high-quality stuff to read.\nI'm collecting my <a href=\"../recommendations/\">recommendations</a>. Mostly people I actually know and then a <em>few</em> other sources I really like.\nIt's easy to add a news feed that pushes 20 news stories every day.\nThat defeats the whole purpose. You will be drowning in noise.</p>\n<p><strong>Prune your feed</strong>\nI have some sources that I have added due to a particularly high-quality post, and if I haven't been impressed with their writing over the next weeks or months, I will remove that feed again.</p>\n<p><strong>Don't try to read everything</strong>\nI think this one is important as well.\nYou don't owe any of these writers your attention.\nWhen I ignore or forget my RSS reader for a week (or a month), and then get back to dozens or hundreds of unread stories, I will usually just mark everything older than some date \"read\".\nMaybe there was some gold hiding there, but there is no way I'm going to find it in the pile of random stuff.\nThe internet is not lacking in content. Trying to read everything is a permanent backlog out of your control. Avoid that!</p>\n<p><strong>Don't feel obligated to read the post completely</strong>\nI practice this mostly for <a href=\"https://thezvi.substack.com/\">Zvi's</a> notoriously long AI newsletters, but it is also true for anything else.\nThey are very interesting, and I enjoy them a lot, but not enough to read a short novel several times a week.\nSkim, skip, consider asking your AI if there are sections you should focus on given your interests.\nZvi also marks the most interesting sections with a bold heading in the Table of Contents. Thanks!</p>", "image": null, "date_published": "2025-11-25T00:00:00+00:00", "authors": [], "tags": null}, {"id": "https://kplauritzen.dk/2025-11-22-homelab-metrics/", "url": "https://kplauritzen.dk/2025-11-22-homelab-metrics/", "title": "Monitoring usage metrics on the homelab", "content_html": "<h1 id=\"monitoring-usage-metrics-on-the-homelab\">Monitoring usage metrics on the homelab</h1>\n<p>I have a very small and underpowered kubernetes cluster at home (see <a href=\"../2025-01-18-homelab-1/\">Homelab #1</a> and <a href=\"../2025-01-24-homelab-2/\">Homelab #2</a>), and I'm always concerned that I'm overutilizing its resources when I want to add another service to it. </p>\n<p>For example, sometimes my FreshRSS site is not responding. Is that because there is a network problem, or am I running out of memory, or something completely different. </p>\n<p>One option could be to run Prometheus and Grafana, scraping metrics from each service at regular intervals. The problem with that is that I'm already running on a low-power system, so spending a significant chunk of the total resources only on MONITORING how many resources I'm using seems like a bad idea. </p>\n<p>Instead, partly inspired by <a href=\"https://www.maragu.dev/blog/go-and-sqlite-in-the-cloud\">Markus' love for SQLite</a>, I'm doing a much simpler thing: </p>\n<ol>\n<li>Run <code>kubectl top nodes</code> and <code>kubectl top pods</code> every 5 minutes with an entry in <code>crontab</code>. </li>\n<li>Do a very simple parsing of the output</li>\n<li>Insert it into a SQLite database with a timestamp. </li>\n</ol>\n<p>The database is a local file, stored on the same computer running <code>k3s</code>. When I want to look at how my system has been behaving recently, I can copy the whole database to my desktop and start digging into it. </p>\n<p>No fuss, minimal overhead, no fancy dashboard. It's weird to do YAGNI on my homelab, because I don't NEED any of it. </p>\n<h2 id=\"appendix\">Appendix:</h2>\n<p>The script I use to collect metrics:\n<div class=\"language-bash highlight\"><pre><span></span><code><span id=\"__span-0-1\"><a id=\"__codelineno-0-1\" name=\"__codelineno-0-1\" href=\"#__codelineno-0-1\"></a><span class=\"ch\">#!/bin/bash</span>\n</span><span id=\"__span-0-2\"><a id=\"__codelineno-0-2\" name=\"__codelineno-0-2\" href=\"#__codelineno-0-2\"></a><span class=\"nb\">set</span><span class=\"w\"> </span>-euo<span class=\"w\"> </span>pipefail\n</span><span id=\"__span-0-3\"><a id=\"__codelineno-0-3\" name=\"__codelineno-0-3\" href=\"#__codelineno-0-3\"></a>\n</span><span id=\"__span-0-4\"><a id=\"__codelineno-0-4\" name=\"__codelineno-0-4\" href=\"#__codelineno-0-4\"></a><span class=\"c1\"># Database location</span>\n</span><span id=\"__span-0-5\"><a id=\"__codelineno-0-5\" name=\"__codelineno-0-5\" href=\"#__codelineno-0-5\"></a><span class=\"nv\">DB_PATH</span><span class=\"o\">=</span><span class=\"s2\">&quot;</span><span class=\"si\">${</span><span class=\"nv\">DB_PATH</span><span class=\"k\">:-</span><span class=\"p\">/var/lib/k8s-metrics/metrics.db</span><span class=\"si\">}</span><span class=\"s2\">&quot;</span>\n</span><span id=\"__span-0-6\"><a id=\"__codelineno-0-6\" name=\"__codelineno-0-6\" href=\"#__codelineno-0-6\"></a><span class=\"nv\">TIMESTAMP</span><span class=\"o\">=</span><span class=\"k\">$(</span>date<span class=\"w\"> </span>-u<span class=\"w\"> </span>+<span class=\"s2\">&quot;%Y-%m-%d %H:%M:%S&quot;</span><span class=\"k\">)</span>\n</span><span id=\"__span-0-7\"><a id=\"__codelineno-0-7\" name=\"__codelineno-0-7\" href=\"#__codelineno-0-7\"></a>\n</span><span id=\"__span-0-8\"><a id=\"__codelineno-0-8\" name=\"__codelineno-0-8\" href=\"#__codelineno-0-8\"></a><span class=\"c1\"># Full path to kubectl (k3s)</span>\n</span><span id=\"__span-0-9\"><a id=\"__codelineno-0-9\" name=\"__codelineno-0-9\" href=\"#__codelineno-0-9\"></a><span class=\"nv\">KUBECTL</span><span class=\"o\">=</span><span class=\"s2\">&quot;</span><span class=\"si\">${</span><span class=\"nv\">KUBECTL</span><span class=\"k\">:-</span><span class=\"p\">/usr/local/bin/kubectl</span><span class=\"si\">}</span><span class=\"s2\">&quot;</span>\n</span><span id=\"__span-0-10\"><a id=\"__codelineno-0-10\" name=\"__codelineno-0-10\" href=\"#__codelineno-0-10\"></a>\n</span><span id=\"__span-0-11\"><a id=\"__codelineno-0-11\" name=\"__codelineno-0-11\" href=\"#__codelineno-0-11\"></a><span class=\"c1\"># Ensure database directory exists</span>\n</span><span id=\"__span-0-12\"><a id=\"__codelineno-0-12\" name=\"__codelineno-0-12\" href=\"#__codelineno-0-12\"></a>mkdir<span class=\"w\"> </span>-p<span class=\"w\"> </span><span class=\"s2\">&quot;</span><span class=\"k\">$(</span>dirname<span class=\"w\"> </span><span class=\"s2\">&quot;</span><span class=\"nv\">$DB_PATH</span><span class=\"s2\">&quot;</span><span class=\"k\">)</span><span class=\"s2\">&quot;</span>\n</span><span id=\"__span-0-13\"><a id=\"__codelineno-0-13\" name=\"__codelineno-0-13\" href=\"#__codelineno-0-13\"></a>\n</span><span id=\"__span-0-14\"><a id=\"__codelineno-0-14\" name=\"__codelineno-0-14\" href=\"#__codelineno-0-14\"></a><span class=\"c1\"># Initialize database if it doesn&#39;t exist</span>\n</span><span id=\"__span-0-15\"><a id=\"__codelineno-0-15\" name=\"__codelineno-0-15\" href=\"#__codelineno-0-15\"></a>sqlite3<span class=\"w\"> </span><span class=\"s2\">&quot;</span><span class=\"nv\">$DB_PATH</span><span class=\"s2\">&quot;</span><span class=\"w\"> </span><span class=\"s\">&lt;&lt;EOF</span>\n</span><span id=\"__span-0-16\"><a id=\"__codelineno-0-16\" name=\"__codelineno-0-16\" href=\"#__codelineno-0-16\"></a><span class=\"s\">CREATE TABLE IF NOT EXISTS node_metrics (</span>\n</span><span id=\"__span-0-17\"><a id=\"__codelineno-0-17\" name=\"__codelineno-0-17\" href=\"#__codelineno-0-17\"></a><span class=\"s\">    timestamp TEXT NOT NULL,</span>\n</span><span id=\"__span-0-18\"><a id=\"__codelineno-0-18\" name=\"__codelineno-0-18\" href=\"#__codelineno-0-18\"></a><span class=\"s\">    node_name TEXT NOT NULL,</span>\n</span><span id=\"__span-0-19\"><a id=\"__codelineno-0-19\" name=\"__codelineno-0-19\" href=\"#__codelineno-0-19\"></a><span class=\"s\">    cpu_cores TEXT NOT NULL,</span>\n</span><span id=\"__span-0-20\"><a id=\"__codelineno-0-20\" name=\"__codelineno-0-20\" href=\"#__codelineno-0-20\"></a><span class=\"s\">    cpu_percent TEXT NOT NULL,</span>\n</span><span id=\"__span-0-21\"><a id=\"__codelineno-0-21\" name=\"__codelineno-0-21\" href=\"#__codelineno-0-21\"></a><span class=\"s\">    memory_bytes TEXT NOT NULL,</span>\n</span><span id=\"__span-0-22\"><a id=\"__codelineno-0-22\" name=\"__codelineno-0-22\" href=\"#__codelineno-0-22\"></a><span class=\"s\">    memory_percent TEXT NOT NULL,</span>\n</span><span id=\"__span-0-23\"><a id=\"__codelineno-0-23\" name=\"__codelineno-0-23\" href=\"#__codelineno-0-23\"></a><span class=\"s\">    PRIMARY KEY (timestamp, node_name)</span>\n</span><span id=\"__span-0-24\"><a id=\"__codelineno-0-24\" name=\"__codelineno-0-24\" href=\"#__codelineno-0-24\"></a><span class=\"s\">);</span>\n</span><span id=\"__span-0-25\"><a id=\"__codelineno-0-25\" name=\"__codelineno-0-25\" href=\"#__codelineno-0-25\"></a>\n</span><span id=\"__span-0-26\"><a id=\"__codelineno-0-26\" name=\"__codelineno-0-26\" href=\"#__codelineno-0-26\"></a><span class=\"s\">CREATE TABLE IF NOT EXISTS pod_metrics (</span>\n</span><span id=\"__span-0-27\"><a id=\"__codelineno-0-27\" name=\"__codelineno-0-27\" href=\"#__codelineno-0-27\"></a><span class=\"s\">    timestamp TEXT NOT NULL,</span>\n</span><span id=\"__span-0-28\"><a id=\"__codelineno-0-28\" name=\"__codelineno-0-28\" href=\"#__codelineno-0-28\"></a><span class=\"s\">    namespace TEXT NOT NULL,</span>\n</span><span id=\"__span-0-29\"><a id=\"__codelineno-0-29\" name=\"__codelineno-0-29\" href=\"#__codelineno-0-29\"></a><span class=\"s\">    pod_name TEXT NOT NULL,</span>\n</span><span id=\"__span-0-30\"><a id=\"__codelineno-0-30\" name=\"__codelineno-0-30\" href=\"#__codelineno-0-30\"></a><span class=\"s\">    cpu_cores TEXT NOT NULL,</span>\n</span><span id=\"__span-0-31\"><a id=\"__codelineno-0-31\" name=\"__codelineno-0-31\" href=\"#__codelineno-0-31\"></a><span class=\"s\">    memory_bytes TEXT NOT NULL,</span>\n</span><span id=\"__span-0-32\"><a id=\"__codelineno-0-32\" name=\"__codelineno-0-32\" href=\"#__codelineno-0-32\"></a><span class=\"s\">    PRIMARY KEY (timestamp, namespace, pod_name)</span>\n</span><span id=\"__span-0-33\"><a id=\"__codelineno-0-33\" name=\"__codelineno-0-33\" href=\"#__codelineno-0-33\"></a><span class=\"s\">);</span>\n</span><span id=\"__span-0-34\"><a id=\"__codelineno-0-34\" name=\"__codelineno-0-34\" href=\"#__codelineno-0-34\"></a>\n</span><span id=\"__span-0-35\"><a id=\"__codelineno-0-35\" name=\"__codelineno-0-35\" href=\"#__codelineno-0-35\"></a><span class=\"s\">CREATE INDEX IF NOT EXISTS idx_node_timestamp ON node_metrics(timestamp);</span>\n</span><span id=\"__span-0-36\"><a id=\"__codelineno-0-36\" name=\"__codelineno-0-36\" href=\"#__codelineno-0-36\"></a><span class=\"s\">CREATE INDEX IF NOT EXISTS idx_pod_timestamp ON pod_metrics(timestamp);</span>\n</span><span id=\"__span-0-37\"><a id=\"__codelineno-0-37\" name=\"__codelineno-0-37\" href=\"#__codelineno-0-37\"></a><span class=\"s\">EOF</span>\n</span><span id=\"__span-0-38\"><a id=\"__codelineno-0-38\" name=\"__codelineno-0-38\" href=\"#__codelineno-0-38\"></a>\n</span><span id=\"__span-0-39\"><a id=\"__codelineno-0-39\" name=\"__codelineno-0-39\" href=\"#__codelineno-0-39\"></a><span class=\"c1\"># Collect node metrics</span>\n</span><span id=\"__span-0-40\"><a id=\"__codelineno-0-40\" name=\"__codelineno-0-40\" href=\"#__codelineno-0-40\"></a><span class=\"nv\">$KUBECTL</span><span class=\"w\"> </span>top<span class=\"w\"> </span>nodes<span class=\"w\"> </span>--no-headers<span class=\"w\"> </span><span class=\"p\">|</span><span class=\"w\"> </span><span class=\"k\">while</span><span class=\"w\"> </span><span class=\"nb\">read</span><span class=\"w\"> </span>-r<span class=\"w\"> </span>name<span class=\"w\"> </span>cpu<span class=\"w\"> </span>cpu_pct<span class=\"w\"> </span>memory<span class=\"w\"> </span>memory_pct<span class=\"p\">;</span><span class=\"w\"> </span><span class=\"k\">do</span>\n</span><span id=\"__span-0-41\"><a id=\"__codelineno-0-41\" name=\"__codelineno-0-41\" href=\"#__codelineno-0-41\"></a><span class=\"w\">    </span>sqlite3<span class=\"w\"> </span><span class=\"s2\">&quot;</span><span class=\"nv\">$DB_PATH</span><span class=\"s2\">&quot;</span><span class=\"w\"> </span><span class=\"s\">&lt;&lt;EOF</span>\n</span><span id=\"__span-0-42\"><a id=\"__codelineno-0-42\" name=\"__codelineno-0-42\" href=\"#__codelineno-0-42\"></a><span class=\"s\">INSERT INTO node_metrics (timestamp, node_name, cpu_cores, cpu_percent, memory_bytes, memory_percent)</span>\n</span><span id=\"__span-0-43\"><a id=\"__codelineno-0-43\" name=\"__codelineno-0-43\" href=\"#__codelineno-0-43\"></a><span class=\"s\">VALUES (&#39;$TIMESTAMP&#39;, &#39;$name&#39;, &#39;$cpu&#39;, &#39;$cpu_pct&#39;, &#39;$memory&#39;, &#39;$memory_pct&#39;);</span>\n</span><span id=\"__span-0-44\"><a id=\"__codelineno-0-44\" name=\"__codelineno-0-44\" href=\"#__codelineno-0-44\"></a><span class=\"s\">EOF</span>\n</span><span id=\"__span-0-45\"><a id=\"__codelineno-0-45\" name=\"__codelineno-0-45\" href=\"#__codelineno-0-45\"></a><span class=\"k\">done</span>\n</span><span id=\"__span-0-46\"><a id=\"__codelineno-0-46\" name=\"__codelineno-0-46\" href=\"#__codelineno-0-46\"></a>\n</span><span id=\"__span-0-47\"><a id=\"__codelineno-0-47\" name=\"__codelineno-0-47\" href=\"#__codelineno-0-47\"></a><span class=\"c1\"># Collect pod metrics</span>\n</span><span id=\"__span-0-48\"><a id=\"__codelineno-0-48\" name=\"__codelineno-0-48\" href=\"#__codelineno-0-48\"></a><span class=\"nv\">$KUBECTL</span><span class=\"w\"> </span>top<span class=\"w\"> </span>pods<span class=\"w\"> </span>-A<span class=\"w\"> </span>--no-headers<span class=\"w\"> </span><span class=\"p\">|</span><span class=\"w\"> </span><span class=\"k\">while</span><span class=\"w\"> </span><span class=\"nb\">read</span><span class=\"w\"> </span>-r<span class=\"w\"> </span>namespace<span class=\"w\"> </span>name<span class=\"w\"> </span>cpu<span class=\"w\"> </span>memory<span class=\"p\">;</span><span class=\"w\"> </span><span class=\"k\">do</span>\n</span><span id=\"__span-0-49\"><a id=\"__codelineno-0-49\" name=\"__codelineno-0-49\" href=\"#__codelineno-0-49\"></a><span class=\"w\">    </span>sqlite3<span class=\"w\"> </span><span class=\"s2\">&quot;</span><span class=\"nv\">$DB_PATH</span><span class=\"s2\">&quot;</span><span class=\"w\"> </span><span class=\"s\">&lt;&lt;EOF</span>\n</span><span id=\"__span-0-50\"><a id=\"__codelineno-0-50\" name=\"__codelineno-0-50\" href=\"#__codelineno-0-50\"></a><span class=\"s\">INSERT INTO pod_metrics (timestamp, namespace, pod_name, cpu_cores, memory_bytes)</span>\n</span><span id=\"__span-0-51\"><a id=\"__codelineno-0-51\" name=\"__codelineno-0-51\" href=\"#__codelineno-0-51\"></a><span class=\"s\">VALUES (&#39;$TIMESTAMP&#39;, &#39;$namespace&#39;, &#39;$name&#39;, &#39;$cpu&#39;, &#39;$memory&#39;);</span>\n</span><span id=\"__span-0-52\"><a id=\"__codelineno-0-52\" name=\"__codelineno-0-52\" href=\"#__codelineno-0-52\"></a><span class=\"s\">EOF</span>\n</span><span id=\"__span-0-53\"><a id=\"__codelineno-0-53\" name=\"__codelineno-0-53\" href=\"#__codelineno-0-53\"></a><span class=\"k\">done</span>\n</span><span id=\"__span-0-54\"><a id=\"__codelineno-0-54\" name=\"__codelineno-0-54\" href=\"#__codelineno-0-54\"></a>\n</span><span id=\"__span-0-55\"><a id=\"__codelineno-0-55\" name=\"__codelineno-0-55\" href=\"#__codelineno-0-55\"></a><span class=\"nb\">echo</span><span class=\"w\"> </span><span class=\"s2\">&quot;Metrics collected at </span><span class=\"nv\">$TIMESTAMP</span><span class=\"s2\">&quot;</span>\n</span></code></pre></div></p>\n<p>The crontab entry\n<div class=\"language-text highlight\"><pre><span></span><code><span id=\"__span-1-1\"><a id=\"__codelineno-1-1\" name=\"__codelineno-1-1\" href=\"#__codelineno-1-1\"></a>*/5 * * * * KUBECONFIG=/home/kasper/.kube/config DB_PATH=/home/kasper/k8s-metrics/metrics.db /home/kasper/k8s-metrics/collect-metrics.sh &gt;&gt; /home/kasper/k8s-metrics/collect.log 2&gt;&amp;1\n</span></code></pre></div></p>", "image": null, "date_published": "2025-11-22T00:00:00+00:00", "authors": [], "tags": null}, {"id": "https://kplauritzen.dk/2025-02-26-claude-37-strawberry/", "url": "https://kplauritzen.dk/2025-02-26-claude-37-strawberry/", "title": "Antropics Claude 3.7 Sonnet is released", "content_html": "<h1 id=\"antropics-claude-37-sonnet-is-released\">Antropics Claude 3.7 Sonnet is released</h1>\n<p>Yesterday, Anthropic released their new LLM, <a href=\"http://www.anthropic.com/news/claude-3-7-sonnet\">Claude 3.7 Sonnet</a>.</p>\n<p>For a run-through of the vibes, I always wait for <a href=\"https://thezvi.substack.com/p/time-to-welcome-claude-37/\">Zvi Mowshowitz</a>'s review.</p>\n<p>However, there are a few fun highlights here. </p>\n<h2 id=\"claudeplayspokemon\">ClaudePlaysPokemon</h2>\n<p>Back in the pre-historic era when I was at Uni, I lost a lot of time to <a href=\"https://en.wikipedia.org/wiki/Twitch_Plays_Pok%C3%A9mon\">Twitch Plays Pokemon</a>.\nI was facinated by the idea of a collective playing a game, and the chaos that ensued. A whole mythos got built up, and it was a lot of fun to follow and occasionally spam in the chat. </p>\n<p>Anthropic has started using \"How far can Claude get in Pokemon\" as one of their benchmarks, but they have also set up an emulator streaming on Twitch.</p>\n<p><img alt=\"Claude can get Surges Badge\" src=\"../images/claude-pokemon.png\" /></p>\n<p>You can watch <a href=\"https://www.twitch.tv/claudeplayspokemon\">ClaudePlaysPokemon</a> and see how far the model can get.</p>\n<h2 id=\"how-many-rs-in-strawberry\">How many \"R\"s in Strawberry?</h2>\n<p>There is an easter egg in the system prompt, where if you ask \"How many R's in Strawberry?\" it will generate a little website that counts the number of R's in the word strawberry.</p>\n<p>I have pasted the HTML directly below (simultaneously testing how markdown with HTML gets handled by MkDocs)</p>\n<!DOCTYPE html>\n<html>\n<body>\n  <!-- Strawberry Counter (isolated styling) -->\n  <div style=\"text-align: center; padding: 20px; margin: 20px auto; max-width: 400px;\">\n    <div id=\"strawberry\" style=\"font-size: 60px; cursor: pointer; margin-bottom: 10px;\">\ud83c\udf53</div>\n    <div id=\"click-text\" style=\"font-size: 16px;\">Click the strawberry to count!</div>\n    <div id=\"word\" style=\"font-size: 28px; margin: 20px 0; visibility: hidden;\"></div>\n    <div id=\"result\" style=\"font-size: 20px; font-weight: bold; color: #e53e3e; visibility: hidden;\"></div>\n  </div>\n\n  <!-- Add the keyframes for bounce animation -->\n<style>\n  @keyframes bounce {\n    from { transform: translateY(0); }\n    to { transform: translateY(-5px); }\n  }\n</style>\n\n<script>\n    const word = \"strawberry\";\n    const wordElement = document.getElementById(\"word\");\n    const resultElement = document.getElementById(\"result\");\n    const strawberryElement = document.getElementById(\"strawberry\");\n    const clickTextElement = document.getElementById(\"click-text\");\n\n    // Calculate the number of Rs\n    const rCount = word.split('').filter(letter => \n      letter.toLowerCase() === 'r'\n    ).length;\n\n    // Set up the click event\n    strawberryElement.addEventListener(\"click\", function() {\n      // Hide the click text\n      clickTextElement.style.display = \"none\";\n\n      // Create the letter-by-letter display\n      wordElement.innerHTML = \"\";\n      for(let i = 0; i < word.length; i++) {\n        const letterSpan = document.createElement(\"span\");\n        letterSpan.textContent = word[i];\n        letterSpan.style.display = \"inline-block\";\n        letterSpan.style.margin = \"0 2px\";\n\n        if(word[i].toLowerCase() === 'r') {\n          letterSpan.style.color = \"#e53e3e\";\n          letterSpan.style.fontWeight = \"bold\";\n          // Add bounce animation with inline styling\n          letterSpan.style.animation = \"bounce 0.5s infinite alternate\";\n          letterSpan.style.display = \"inline-block\";\n        }\n\n        wordElement.appendChild(letterSpan);\n      }\n\n      // Show the word and result\n      wordElement.style.visibility = \"visible\";\n      resultElement.textContent = `There are ${rCount} R's in strawberry!`;\n      resultElement.style.visibility = \"visible\";\n    });\n  </script>\n</body>\n</html>", "image": null, "date_published": "2025-02-26T00:00:00+00:00", "authors": [], "tags": null}, {"id": "https://kplauritzen.dk/2025-02-05-mastodon-rss/", "url": "https://kplauritzen.dk/2025-02-05-mastodon-rss/", "title": "How to follow Mastodon accounts using an RSS feed", "content_html": "<h1 id=\"how-to-follow-mastodon-accounts-using-an-rss-feed\">How to follow Mastodon accounts using an RSS feed</h1>\n<p>I've been really happy with my self-hosted RSS reader <a href=\"https://freshrss.org/index.html\">FreshRSS</a> .\nI've consolidated a lot of the newsletter spam and occasional checking of blogs with a more considered decision to go to the RSS reader.</p>\n<p>This means that I have had less desire to check out whats going on on Mastodon.</p>\n<p>I just found out that I can get an RSS feed for Mastodon accounts. So, if I wanted to follow <a href=\"https://hachyderm.io/@charliermarsh\">@charliemarsh</a> I can just append <code>.rss</code> to the url of his profile webpage. (Note, this has to be his <a href=\"https://fedi.tips/what-are-original-pages-in-mastodon/\">original page</a>).\nSo, Charlies original page is <code>https://hachyderm.io/@charliermarsh</code> and the RSS feed with all his public posts are available at <code>https://hachyderm.io/@charliermarsh.rss</code>.</p>\n<p>I have started to follow a few Python people where I'm not aware of any blog to follow instead. Aside from Charlie Marsh, I also now follow:</p>\n<ul>\n<li><a href=\"https://fosstodon.org/@samuelcolvin\">Samuel Colvin</a></li>\n<li><a href=\"https://fosstodon.org/@tiangolo\">Sebasti\u00e1n Ram\u00edrez</a></li>\n</ul>\n<p>I also have an account that I barely use at <a href=\"https://hachyderm.io/@kplauritzen\">@KPLauritzen</a> that you can follow.</p>\n<p>Via <a href=\"https://fedi.tips/following-mastodon-and-fediverse-accounts-through-rss/\">Feditips</a></p>", "image": null, "date_published": "2025-02-05T00:00:00+00:00", "authors": [], "tags": null}, {"id": "https://kplauritzen.dk/2025-02-04-terraform-destructive-pr/", "url": "https://kplauritzen.dk/2025-02-04-terraform-destructive-pr/", "title": "Warn about destructive Terraform changes in pull requests", "content_html": "<h1 id=\"warn-about-destructive-terraform-changes-in-pull-requests\">Warn about destructive Terraform changes in pull requests</h1>\n<h2 id=\"the-problem\">The problem</h2>\n<p>When automating infrastructure changes through CI/CD pipelines, it can be VERY scary to merge a pull request that changes something in your infrastructure that you are not very familiar with.</p>\n<p>Sure, you have tested the change in a test environment. Did you try to make a plan against a dev environment too? Have you tested against all the relevant targets of this change?</p>\n<p>Maybe you are reviewing someone else's infrastructure changes. How can you be sure you have caught if this is actually destroying and recreating all the databases?</p>\n<p>I've dealt with too much of this anxiety! AUTOMATE IT AWAY!</p>\n<p>With some inspiration from my friend, <a href=\"https://dk.linkedin.com/in/lasse-hels\">Lasse Hels</a>, I created this bash script for Azure DevOps Pipelines.</p>\n<h2 id=\"what-it-does\">What it does</h2>\n<ul>\n<li>It is assumed to be running as part of a pull request validation pipeline</li>\n<li>Assuming there is a terraform plan created in the file <code>tfplan</code>, it parses the plan as plaintext and json</li>\n<li>No matter what, the plaintext plan is posted as a comment in the pull request. The comment will be collapsed by default.</li>\n<li>If it finds any destructive changes in the plan, the comment will have a big scary warning and be marked as \"Active\". This means someone will have to look at it and resolve it before the pull request can be merged.</li>\n</ul>\n<h2 id=\"the-script\">The script</h2>\n<div class=\"language-shell highlight\"><pre><span></span><code><span id=\"__span-0-1\"><a id=\"__codelineno-0-1\" name=\"__codelineno-0-1\" href=\"#__codelineno-0-1\"></a><span class=\"ch\">#!/bin/bash</span>\n</span><span id=\"__span-0-2\"><a id=\"__codelineno-0-2\" name=\"__codelineno-0-2\" href=\"#__codelineno-0-2\"></a><span class=\"nb\">set</span><span class=\"w\"> </span>-euo<span class=\"w\"> </span>pipefail\n</span><span id=\"__span-0-3\"><a id=\"__codelineno-0-3\" name=\"__codelineno-0-3\" href=\"#__codelineno-0-3\"></a>\n</span><span id=\"__span-0-4\"><a id=\"__codelineno-0-4\" name=\"__codelineno-0-4\" href=\"#__codelineno-0-4\"></a><span class=\"c1\"># Somehow create your plan as tfplan</span>\n</span><span id=\"__span-0-5\"><a id=\"__codelineno-0-5\" name=\"__codelineno-0-5\" href=\"#__codelineno-0-5\"></a><span class=\"nv\">PLAN_TEXT</span><span class=\"o\">=</span><span class=\"k\">$(</span>terraform<span class=\"w\"> </span>show<span class=\"w\"> </span>tfplan<span class=\"k\">)</span>\n</span><span id=\"__span-0-6\"><a id=\"__codelineno-0-6\" name=\"__codelineno-0-6\" href=\"#__codelineno-0-6\"></a><span class=\"nv\">PLAN_JSON</span><span class=\"o\">=</span><span class=\"k\">$(</span>terraform<span class=\"w\"> </span>show<span class=\"w\"> </span>-json<span class=\"w\"> </span>tfplan<span class=\"k\">)</span>\n</span><span id=\"__span-0-7\"><a id=\"__codelineno-0-7\" name=\"__codelineno-0-7\" href=\"#__codelineno-0-7\"></a><span class=\"nv\">HAS_DESTRUCTIVE_CHANGES</span><span class=\"o\">=</span><span class=\"k\">$(</span><span class=\"nb\">echo</span><span class=\"w\"> </span><span class=\"s2\">&quot;</span><span class=\"nv\">$PLAN_JSON</span><span class=\"s2\">&quot;</span><span class=\"w\"> </span><span class=\"p\">|</span><span class=\"w\"> </span>jq<span class=\"w\"> </span>-r<span class=\"w\"> </span><span class=\"s1\">&#39;.resource_changes[] | select(.change.actions[] | contains(&quot;delete&quot;))&#39;</span><span class=\"k\">)</span>\n</span><span id=\"__span-0-8\"><a id=\"__codelineno-0-8\" name=\"__codelineno-0-8\" href=\"#__codelineno-0-8\"></a>\n</span><span id=\"__span-0-9\"><a id=\"__codelineno-0-9\" name=\"__codelineno-0-9\" href=\"#__codelineno-0-9\"></a><span class=\"c1\"># Conditional alert</span>\n</span><span id=\"__span-0-10\"><a id=\"__codelineno-0-10\" name=\"__codelineno-0-10\" href=\"#__codelineno-0-10\"></a><span class=\"nv\">DANGER_MESSAGE</span><span class=\"o\">=</span><span class=\"s2\">&quot;&quot;</span>\n</span><span id=\"__span-0-11\"><a id=\"__codelineno-0-11\" name=\"__codelineno-0-11\" href=\"#__codelineno-0-11\"></a><span class=\"k\">if</span><span class=\"w\"> </span><span class=\"o\">[</span><span class=\"w\"> </span>!<span class=\"w\"> </span>-z<span class=\"w\"> </span><span class=\"s2\">&quot;</span><span class=\"nv\">$HAS_DESTRUCTIVE_CHANGES</span><span class=\"s2\">&quot;</span><span class=\"w\"> </span><span class=\"o\">]</span><span class=\"p\">;</span><span class=\"w\"> </span><span class=\"k\">then</span>\n</span><span id=\"__span-0-12\"><a id=\"__codelineno-0-12\" name=\"__codelineno-0-12\" href=\"#__codelineno-0-12\"></a><span class=\"w\"> </span><span class=\"nv\">DANGER_MESSAGE</span><span class=\"o\">=</span><span class=\"s2\">&quot;**DANGER! YOU ARE ABOUT TO DESTROY RESOURCES**&quot;</span>\n</span><span id=\"__span-0-13\"><a id=\"__codelineno-0-13\" name=\"__codelineno-0-13\" href=\"#__codelineno-0-13\"></a><span class=\"k\">fi</span>\n</span><span id=\"__span-0-14\"><a id=\"__codelineno-0-14\" name=\"__codelineno-0-14\" href=\"#__codelineno-0-14\"></a>\n</span><span id=\"__span-0-15\"><a id=\"__codelineno-0-15\" name=\"__codelineno-0-15\" href=\"#__codelineno-0-15\"></a><span class=\"c1\"># Actual comment to be posted</span>\n</span><span id=\"__span-0-16\"><a id=\"__codelineno-0-16\" name=\"__codelineno-0-16\" href=\"#__codelineno-0-16\"></a><span class=\"nv\">CODE_BLOCK_FENCE</span><span class=\"o\">=</span><span class=\"s1\">&#39;```&#39;</span>\n</span><span id=\"__span-0-17\"><a id=\"__codelineno-0-17\" name=\"__codelineno-0-17\" href=\"#__codelineno-0-17\"></a><span class=\"nv\">COMMENT</span><span class=\"o\">=</span><span class=\"k\">$(</span>cat<span class=\"w\"> </span><span class=\"s\">&lt;&lt; EOF</span>\n</span><span id=\"__span-0-18\"><a id=\"__codelineno-0-18\" name=\"__codelineno-0-18\" href=\"#__codelineno-0-18\"></a><span class=\"s\">${DANGER_MESSAGE}</span>\n</span><span id=\"__span-0-19\"><a id=\"__codelineno-0-19\" name=\"__codelineno-0-19\" href=\"#__codelineno-0-19\"></a><span class=\"s\">&lt;details&gt;&lt;summary&gt;Click to expand&lt;/summary&gt;</span>\n</span><span id=\"__span-0-20\"><a id=\"__codelineno-0-20\" name=\"__codelineno-0-20\" href=\"#__codelineno-0-20\"></a>\n</span><span id=\"__span-0-21\"><a id=\"__codelineno-0-21\" name=\"__codelineno-0-21\" href=\"#__codelineno-0-21\"></a><span class=\"s\">${CODE_BLOCK_FENCE}</span>\n</span><span id=\"__span-0-22\"><a id=\"__codelineno-0-22\" name=\"__codelineno-0-22\" href=\"#__codelineno-0-22\"></a><span class=\"s\">${PLAN_TEXT}</span>\n</span><span id=\"__span-0-23\"><a id=\"__codelineno-0-23\" name=\"__codelineno-0-23\" href=\"#__codelineno-0-23\"></a><span class=\"s\">${CODE_BLOCK_FENCE}</span>\n</span><span id=\"__span-0-24\"><a id=\"__codelineno-0-24\" name=\"__codelineno-0-24\" href=\"#__codelineno-0-24\"></a><span class=\"s\">&lt;/details&gt;</span>\n</span><span id=\"__span-0-25\"><a id=\"__codelineno-0-25\" name=\"__codelineno-0-25\" href=\"#__codelineno-0-25\"></a><span class=\"s\">EOF</span>\n</span><span id=\"__span-0-26\"><a id=\"__codelineno-0-26\" name=\"__codelineno-0-26\" href=\"#__codelineno-0-26\"></a><span class=\"k\">)</span>\n</span><span id=\"__span-0-27\"><a id=\"__codelineno-0-27\" name=\"__codelineno-0-27\" href=\"#__codelineno-0-27\"></a>\n</span><span id=\"__span-0-28\"><a id=\"__codelineno-0-28\" name=\"__codelineno-0-28\" href=\"#__codelineno-0-28\"></a><span class=\"c1\"># Set comment status to Active for destructive changes, Resolved otherwise</span>\n</span><span id=\"__span-0-29\"><a id=\"__codelineno-0-29\" name=\"__codelineno-0-29\" href=\"#__codelineno-0-29\"></a><span class=\"nv\">COMMENT_STATUS</span><span class=\"o\">=</span><span class=\"m\">2</span><span class=\"w\"> </span><span class=\"c1\"># Resolved</span>\n</span><span id=\"__span-0-30\"><a id=\"__codelineno-0-30\" name=\"__codelineno-0-30\" href=\"#__codelineno-0-30\"></a><span class=\"k\">if</span><span class=\"w\"> </span><span class=\"o\">[</span><span class=\"w\"> </span>!<span class=\"w\"> </span>-z<span class=\"w\"> </span><span class=\"s2\">&quot;</span><span class=\"nv\">$HAS_DESTRUCTIVE_CHANGES</span><span class=\"s2\">&quot;</span><span class=\"w\"> </span><span class=\"o\">]</span><span class=\"p\">;</span><span class=\"w\"> </span><span class=\"k\">then</span>\n</span><span id=\"__span-0-31\"><a id=\"__codelineno-0-31\" name=\"__codelineno-0-31\" href=\"#__codelineno-0-31\"></a><span class=\"w\"> </span><span class=\"nv\">COMMENT_STATUS</span><span class=\"o\">=</span><span class=\"m\">1</span><span class=\"w\"> </span><span class=\"c1\"># Active</span>\n</span><span id=\"__span-0-32\"><a id=\"__codelineno-0-32\" name=\"__codelineno-0-32\" href=\"#__codelineno-0-32\"></a><span class=\"k\">fi</span>\n</span><span id=\"__span-0-33\"><a id=\"__codelineno-0-33\" name=\"__codelineno-0-33\" href=\"#__codelineno-0-33\"></a>\n</span><span id=\"__span-0-34\"><a id=\"__codelineno-0-34\" name=\"__codelineno-0-34\" href=\"#__codelineno-0-34\"></a><span class=\"c1\"># Build payload for ADO API</span>\n</span><span id=\"__span-0-35\"><a id=\"__codelineno-0-35\" name=\"__codelineno-0-35\" href=\"#__codelineno-0-35\"></a><span class=\"nv\">JSON_PAYLOAD</span><span class=\"o\">=</span><span class=\"k\">$(</span>jq<span class=\"w\"> </span>-n<span class=\"w\"> </span><span class=\"se\">\\</span>\n</span><span id=\"__span-0-36\"><a id=\"__codelineno-0-36\" name=\"__codelineno-0-36\" href=\"#__codelineno-0-36\"></a><span class=\"w\"> </span>--arg<span class=\"w\"> </span>content<span class=\"w\"> </span><span class=\"s2\">&quot;</span><span class=\"nv\">$COMMENT</span><span class=\"s2\">&quot;</span><span class=\"w\"> </span><span class=\"se\">\\</span>\n</span><span id=\"__span-0-37\"><a id=\"__codelineno-0-37\" name=\"__codelineno-0-37\" href=\"#__codelineno-0-37\"></a><span class=\"w\"> </span>--arg<span class=\"w\"> </span>status<span class=\"w\"> </span><span class=\"s2\">&quot;</span><span class=\"nv\">$COMMENT_STATUS</span><span class=\"s2\">&quot;</span><span class=\"w\"> </span><span class=\"se\">\\</span>\n</span><span id=\"__span-0-38\"><a id=\"__codelineno-0-38\" name=\"__codelineno-0-38\" href=\"#__codelineno-0-38\"></a><span class=\"w\"> </span><span class=\"s1\">&#39;{comments: [{content: $content}], status: ($status|tonumber)}&#39;</span>\n</span><span id=\"__span-0-39\"><a id=\"__codelineno-0-39\" name=\"__codelineno-0-39\" href=\"#__codelineno-0-39\"></a><span class=\"k\">)</span>\n</span><span id=\"__span-0-40\"><a id=\"__codelineno-0-40\" name=\"__codelineno-0-40\" href=\"#__codelineno-0-40\"></a>\n</span><span id=\"__span-0-41\"><a id=\"__codelineno-0-41\" name=\"__codelineno-0-41\" href=\"#__codelineno-0-41\"></a><span class=\"c1\"># Call ADO API to make the comment</span>\n</span><span id=\"__span-0-42\"><a id=\"__codelineno-0-42\" name=\"__codelineno-0-42\" href=\"#__codelineno-0-42\"></a>curl<span class=\"w\"> </span>-X<span class=\"w\"> </span>POST<span class=\"w\"> </span><span class=\"se\">\\</span>\n</span><span id=\"__span-0-43\"><a id=\"__codelineno-0-43\" name=\"__codelineno-0-43\" href=\"#__codelineno-0-43\"></a><span class=\"w\"> </span><span class=\"s2\">&quot;</span><span class=\"k\">$(</span>SYSTEM_COLLECTIONURI<span class=\"k\">)</span><span class=\"s2\">/</span><span class=\"k\">$(</span>SYSTEM_TEAMPROJECT<span class=\"k\">)</span><span class=\"s2\">/_apis/git/repositories/</span><span class=\"k\">$(</span>BUILD_REPOSITORY_NAME<span class=\"k\">)</span><span class=\"s2\">/pullrequests/</span><span class=\"k\">$(</span>SYSTEM_PULLREQUEST_PULLREQUESTID<span class=\"k\">)</span><span class=\"s2\">/threads?api-version=6.0&quot;</span><span class=\"w\"> </span><span class=\"se\">\\</span>\n</span><span id=\"__span-0-44\"><a id=\"__codelineno-0-44\" name=\"__codelineno-0-44\" href=\"#__codelineno-0-44\"></a><span class=\"w\"> </span>-H<span class=\"w\"> </span><span class=\"s2\">&quot;Authorization: Bearer </span><span class=\"k\">$(</span>SYSTEM_ACCESSTOKEN<span class=\"k\">)</span><span class=\"s2\">&quot;</span><span class=\"w\"> </span><span class=\"se\">\\</span>\n</span><span id=\"__span-0-45\"><a id=\"__codelineno-0-45\" name=\"__codelineno-0-45\" href=\"#__codelineno-0-45\"></a><span class=\"w\"> </span>-H<span class=\"w\"> </span><span class=\"s2\">&quot;Content-Type: application/json&quot;</span><span class=\"w\"> </span><span class=\"se\">\\</span>\n</span><span id=\"__span-0-46\"><a id=\"__codelineno-0-46\" name=\"__codelineno-0-46\" href=\"#__codelineno-0-46\"></a><span class=\"w\"> </span>-d<span class=\"w\"> </span><span class=\"s2\">&quot;</span><span class=\"nv\">$JSON_PAYLOAD</span><span class=\"s2\">&quot;</span>\n</span></code></pre></div>\n<h2 id=\"references\">References</h2>\n<ul>\n<li><a href=\"https://learn.microsoft.com/en-us/rest/api/azure/devops/?view=azure-devops-rest-7.2&amp;viewFallbackFrom=azure-devops-rest-6.0\">Azure DevOps REST API</a></li>\n<li><a href=\"https://learn.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&amp;tabs=yaml\">Azure pipelines variables</a></li>\n<li><a href=\"https://jqlang.org/\">jq</a></li>\n</ul>", "image": null, "date_published": "2025-02-04T00:00:00+00:00", "authors": [], "tags": null}, {"id": "https://kplauritzen.dk/2025-02-01-uv-resolution/", "url": "https://kplauritzen.dk/2025-02-01-uv-resolution/", "title": "Test dependency bounds with uv run --resolution", "content_html": "<h1 id=\"test-dependency-bounds-with-uv-run-resolution\">Test dependency bounds with <code>uv run --resolution</code></h1>\n<p>I'm distributing a small Python package at work. A small library with some utilities for doing Machine Learning work.\nI'm using <a href=\"https://docs.astral.sh/uv/\">uv</a> to manage the dependencies and the build process.</p>\n<p>Part of my pyproject.toml file looks like this:</p>\n<div class=\"language-toml highlight\"><pre><span></span><code><span id=\"__span-0-1\"><a id=\"__codelineno-0-1\" name=\"__codelineno-0-1\" href=\"#__codelineno-0-1\"></a><span class=\"k\">[project]</span>\n</span><span id=\"__span-0-2\"><a id=\"__codelineno-0-2\" name=\"__codelineno-0-2\" href=\"#__codelineno-0-2\"></a><span class=\"p\">...</span>\n</span><span id=\"__span-0-3\"><a id=\"__codelineno-0-3\" name=\"__codelineno-0-3\" href=\"#__codelineno-0-3\"></a><span class=\"n\">requires-python</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"s2\">&quot;&gt;=3.10,&gt;3.14&quot;</span>\n</span><span id=\"__span-0-4\"><a id=\"__codelineno-0-4\" name=\"__codelineno-0-4\" href=\"#__codelineno-0-4\"></a><span class=\"n\">dependencies</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"p\">[</span>\n</span><span id=\"__span-0-5\"><a id=\"__codelineno-0-5\" name=\"__codelineno-0-5\" href=\"#__codelineno-0-5\"></a><span class=\"w\">    </span><span class=\"s2\">&quot;pydantic&gt;=2.0,&lt;3&quot;</span><span class=\"p\">,</span>\n</span><span id=\"__span-0-6\"><a id=\"__codelineno-0-6\" name=\"__codelineno-0-6\" href=\"#__codelineno-0-6\"></a><span class=\"p\">]</span>\n</span></code></pre></div>\n<p>How do I know that my library will work with both <code>pydantic==2.0</code> and <code>pydantic==2.10</code> (The current version at time of writing)?\nI could just require a much smaller band of possible versions, but I want my library to be useful for as many users as possible.\nAnd they might need to use a different version of <code>pydantic</code> for their own projects.</p>\n<p>Similarly, I want to make sure my library actually works with the range of allowed Python versions.</p>\n<p>I run my tests with <code>uv run pytest</code>. This will use the locked dependencies in the <code>uv.lock</code> file to create a virtual environment and run the tests in that environment.</p>\n<p>But, I can use the <code>--resolution</code> flag to test my library with different versions of the dependencies.\nAccording to the <a href=\"https://docs.astral.sh/uv/reference/cli/#uv-run\">uv documentation</a>, there are three possible values for the <code>--resolution</code> flag:</p>\n<ul>\n<li>highest: Resolve the highest compatible version of each package</li>\n<li>lowest: Resolve the lowest compatible version of each package</li>\n<li>lowest-direct: Resolve the lowest compatible version of any direct dependencies, and the highest compatible version of any transitive dependencies</li>\n</ul>\n<p>I have found that using <code>--resolution lowest</code> is not really that useful, because some transitive dependencies might not specify a version range. Maybe they just require \"numpy\" without specifying a version. In that case, I will be testing my library against <code>numpy==0.0.1</code> or whatever the lowest version is. That is not really useful. Instead, I use <code>--resolution lowest-direct</code> to test against the lowest version of the direct dependencies and then just select the highest version of the transitive dependencies.</p>\n<p>I can also specify the python version to use with the <code>--python</code> flag.</p>\n<p>Finally, I can use the <code>--isolated</code> flag to make sure that the tests are run in an isolated virtual environment, not affecting the active venv of my workspace.</p>\n<p>Here is the entry in my justfile that runs the tests with different dependency resolutions:</p>\n<div class=\"language-make highlight\"><span class=\"filename\">justfile</span><pre><span></span><code><span id=\"__span-1-1\"><a id=\"__codelineno-1-1\" name=\"__codelineno-1-1\" href=\"#__codelineno-1-1\"></a><span class=\"nf\">test_dependency_bounds</span><span class=\"o\">:</span>\n</span><span id=\"__span-1-2\"><a id=\"__codelineno-1-2\" name=\"__codelineno-1-2\" href=\"#__codelineno-1-2\"></a><span class=\"w\">    </span>uv<span class=\"w\"> </span>run<span class=\"w\"> </span>--python<span class=\"w\"> </span><span class=\"m\">3</span>.10<span class=\"w\"> </span>--resolution<span class=\"w\"> </span>lowest-direct<span class=\"w\"> </span>--isolated<span class=\"w\"> </span>pytest\n</span><span id=\"__span-1-3\"><a id=\"__codelineno-1-3\" name=\"__codelineno-1-3\" href=\"#__codelineno-1-3\"></a><span class=\"w\">    </span>uv<span class=\"w\"> </span>run<span class=\"w\"> </span>--python<span class=\"w\"> </span><span class=\"m\">3</span>.13<span class=\"w\"> </span>--resolution<span class=\"w\"> </span>highest<span class=\"w\"> </span>--isolated<span class=\"w\"> </span>pytest\n</span></code></pre></div>", "image": null, "date_published": "2025-02-01T00:00:00+00:00", "authors": [], "tags": null}, {"id": "https://kplauritzen.dk/2025-01-24-homelab-2/", "url": "https://kplauritzen.dk/2025-01-24-homelab-2/", "title": "Homelab: Commiting my secrets to git", "content_html": "<h1 id=\"homelab-commiting-my-secrets-to-git\">Homelab: Commiting my secrets to git</h1>\n<p>I spent some time tonight setting configuring some services on my home kubernetes cluster. See <a href=\"../2025-01-18-homelab-1/\">this post</a> for more details on how I set up the cluster.\nSo far it's been a fun experiment to see if I can avoid anything spontanously catching file. At work there is a full team of experts dedicated to keep our cluster running smoothly. At home, there is... me. </p>\n<p>Today I managed to get <a href=\"https://freshrss.org/\">FreshRSS</a> and <a href=\"https://github.com/ellie/atuin\">Atuin Sync</a> running.</p>\n<p>I've been using <a href=\"https://www.cursor.com/\">Cursor</a> as a guide generating the yaml files and asking questions about how Kubernetes works. I think I am a decent user of Kubernetes clusters, but a rank novice as an operator of a cluster. </p>\n<h2 id=\"freshrss\">FreshRSS</h2>\n<p>I want to try to get away from doomscrolling, and being caught in some algorithmically generated news feed. I'll try FreshRSS for a while at least. </p>\n<p>To get started I asked Cursor to generate a deployment, giving it a link to the <a href=\"https://freshrss.org/docs/installation/docker/\">FreshRSS documentation</a>.</p>\n<p>I had to go back and forth a few times to understand how to get a URL to resolve on my home network. \nThe kubernetes cluster is running on the host <code>tyr</code>, so I can ping that from my home network on <code>tyr.local</code>. </p>\n<p>Initially I wanted to host FreshRSS at <code>rss.tyr.local</code>, but I didn't figure out how to do that. Instead I hosted it at <code>tyr.local/rss</code> and then added Middleware to strip the <code>/rss</code> path before sending the traffic to the Service. </p>\n<details class=\"note\">\n<summary>Complete manifest</summary>\n<div class=\"language-yaml highlight\"><pre><span></span><code><span id=\"__span-0-1\"><a id=\"__codelineno-0-1\" name=\"__codelineno-0-1\" href=\"#__codelineno-0-1\"></a><span class=\"nn\">---</span>\n</span><span id=\"__span-0-2\"><a id=\"__codelineno-0-2\" name=\"__codelineno-0-2\" href=\"#__codelineno-0-2\"></a><span class=\"c1\"># deployment.yaml</span>\n</span><span id=\"__span-0-3\"><a id=\"__codelineno-0-3\" name=\"__codelineno-0-3\" href=\"#__codelineno-0-3\"></a>\n</span><span id=\"__span-0-4\"><a id=\"__codelineno-0-4\" name=\"__codelineno-0-4\" href=\"#__codelineno-0-4\"></a><span class=\"nt\">apiVersion</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">apps/v1</span>\n</span><span id=\"__span-0-5\"><a id=\"__codelineno-0-5\" name=\"__codelineno-0-5\" href=\"#__codelineno-0-5\"></a><span class=\"nt\">kind</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">Deployment</span>\n</span><span id=\"__span-0-6\"><a id=\"__codelineno-0-6\" name=\"__codelineno-0-6\" href=\"#__codelineno-0-6\"></a><span class=\"nt\">metadata</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-7\"><a id=\"__codelineno-0-7\" name=\"__codelineno-0-7\" href=\"#__codelineno-0-7\"></a><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">freshrss</span>\n</span><span id=\"__span-0-8\"><a id=\"__codelineno-0-8\" name=\"__codelineno-0-8\" href=\"#__codelineno-0-8\"></a><span class=\"nt\">namespace</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">freshrss</span>\n</span><span id=\"__span-0-9\"><a id=\"__codelineno-0-9\" name=\"__codelineno-0-9\" href=\"#__codelineno-0-9\"></a><span class=\"nt\">labels</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-10\"><a id=\"__codelineno-0-10\" name=\"__codelineno-0-10\" href=\"#__codelineno-0-10\"></a><span class=\"w\">    </span><span class=\"nt\">app</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">freshrss</span>\n</span><span id=\"__span-0-11\"><a id=\"__codelineno-0-11\" name=\"__codelineno-0-11\" href=\"#__codelineno-0-11\"></a><span class=\"nt\">spec</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-12\"><a id=\"__codelineno-0-12\" name=\"__codelineno-0-12\" href=\"#__codelineno-0-12\"></a><span class=\"nt\">replicas</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">1</span>\n</span><span id=\"__span-0-13\"><a id=\"__codelineno-0-13\" name=\"__codelineno-0-13\" href=\"#__codelineno-0-13\"></a><span class=\"nt\">selector</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-14\"><a id=\"__codelineno-0-14\" name=\"__codelineno-0-14\" href=\"#__codelineno-0-14\"></a><span class=\"w\">    </span><span class=\"nt\">matchLabels</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-15\"><a id=\"__codelineno-0-15\" name=\"__codelineno-0-15\" href=\"#__codelineno-0-15\"></a><span class=\"w\">    </span><span class=\"nt\">app</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">freshrss</span>\n</span><span id=\"__span-0-16\"><a id=\"__codelineno-0-16\" name=\"__codelineno-0-16\" href=\"#__codelineno-0-16\"></a><span class=\"nt\">template</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-17\"><a id=\"__codelineno-0-17\" name=\"__codelineno-0-17\" href=\"#__codelineno-0-17\"></a><span class=\"w\">    </span><span class=\"nt\">metadata</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-18\"><a id=\"__codelineno-0-18\" name=\"__codelineno-0-18\" href=\"#__codelineno-0-18\"></a><span class=\"w\">    </span><span class=\"nt\">labels</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-19\"><a id=\"__codelineno-0-19\" name=\"__codelineno-0-19\" href=\"#__codelineno-0-19\"></a><span class=\"w\">        </span><span class=\"nt\">app</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">freshrss</span>\n</span><span id=\"__span-0-20\"><a id=\"__codelineno-0-20\" name=\"__codelineno-0-20\" href=\"#__codelineno-0-20\"></a><span class=\"w\">    </span><span class=\"nt\">spec</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-21\"><a id=\"__codelineno-0-21\" name=\"__codelineno-0-21\" href=\"#__codelineno-0-21\"></a><span class=\"w\">    </span><span class=\"nt\">containers</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-22\"><a id=\"__codelineno-0-22\" name=\"__codelineno-0-22\" href=\"#__codelineno-0-22\"></a><span class=\"w\">        </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">freshrss</span>\n</span><span id=\"__span-0-23\"><a id=\"__codelineno-0-23\" name=\"__codelineno-0-23\" href=\"#__codelineno-0-23\"></a><span class=\"w\">        </span><span class=\"nt\">image</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">freshrss/freshrss:latest</span>\n</span><span id=\"__span-0-24\"><a id=\"__codelineno-0-24\" name=\"__codelineno-0-24\" href=\"#__codelineno-0-24\"></a><span class=\"w\">        </span><span class=\"nt\">ports</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-25\"><a id=\"__codelineno-0-25\" name=\"__codelineno-0-25\" href=\"#__codelineno-0-25\"></a><span class=\"w\">            </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">containerPort</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">80</span>\n</span><span id=\"__span-0-26\"><a id=\"__codelineno-0-26\" name=\"__codelineno-0-26\" href=\"#__codelineno-0-26\"></a><span class=\"w\">        </span><span class=\"nt\">env</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-27\"><a id=\"__codelineno-0-27\" name=\"__codelineno-0-27\" href=\"#__codelineno-0-27\"></a><span class=\"w\">            </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">TZ</span>\n</span><span id=\"__span-0-28\"><a id=\"__codelineno-0-28\" name=\"__codelineno-0-28\" href=\"#__codelineno-0-28\"></a><span class=\"w\">            </span><span class=\"nt\">value</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s\">&quot;Europe/Copenhagen&quot;</span>\n</span><span id=\"__span-0-29\"><a id=\"__codelineno-0-29\" name=\"__codelineno-0-29\" href=\"#__codelineno-0-29\"></a><span class=\"w\">            </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">CRON_MIN</span>\n</span><span id=\"__span-0-30\"><a id=\"__codelineno-0-30\" name=\"__codelineno-0-30\" href=\"#__codelineno-0-30\"></a><span class=\"w\">            </span><span class=\"nt\">value</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s\">&quot;13,43&quot;</span>\n</span><span id=\"__span-0-31\"><a id=\"__codelineno-0-31\" name=\"__codelineno-0-31\" href=\"#__codelineno-0-31\"></a><span class=\"w\">        </span><span class=\"nt\">volumeMounts</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-32\"><a id=\"__codelineno-0-32\" name=\"__codelineno-0-32\" href=\"#__codelineno-0-32\"></a><span class=\"w\">            </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">data</span>\n</span><span id=\"__span-0-33\"><a id=\"__codelineno-0-33\" name=\"__codelineno-0-33\" href=\"#__codelineno-0-33\"></a><span class=\"w\">            </span><span class=\"nt\">mountPath</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">/var/www/FreshRSS/data</span>\n</span><span id=\"__span-0-34\"><a id=\"__codelineno-0-34\" name=\"__codelineno-0-34\" href=\"#__codelineno-0-34\"></a><span class=\"w\">            </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">extensions</span>\n</span><span id=\"__span-0-35\"><a id=\"__codelineno-0-35\" name=\"__codelineno-0-35\" href=\"#__codelineno-0-35\"></a><span class=\"w\">            </span><span class=\"nt\">mountPath</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">/var/www/FreshRSS/extensions</span>\n</span><span id=\"__span-0-36\"><a id=\"__codelineno-0-36\" name=\"__codelineno-0-36\" href=\"#__codelineno-0-36\"></a><span class=\"w\">        </span><span class=\"nt\">resources</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-37\"><a id=\"__codelineno-0-37\" name=\"__codelineno-0-37\" href=\"#__codelineno-0-37\"></a><span class=\"w\">            </span><span class=\"nt\">requests</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-38\"><a id=\"__codelineno-0-38\" name=\"__codelineno-0-38\" href=\"#__codelineno-0-38\"></a><span class=\"w\">            </span><span class=\"nt\">memory</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s\">&quot;128Mi&quot;</span>\n</span><span id=\"__span-0-39\"><a id=\"__codelineno-0-39\" name=\"__codelineno-0-39\" href=\"#__codelineno-0-39\"></a><span class=\"w\">            </span><span class=\"nt\">cpu</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s\">&quot;100m&quot;</span>\n</span><span id=\"__span-0-40\"><a id=\"__codelineno-0-40\" name=\"__codelineno-0-40\" href=\"#__codelineno-0-40\"></a><span class=\"w\">            </span><span class=\"nt\">limits</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-41\"><a id=\"__codelineno-0-41\" name=\"__codelineno-0-41\" href=\"#__codelineno-0-41\"></a><span class=\"w\">            </span><span class=\"nt\">memory</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s\">&quot;256Mi&quot;</span>\n</span><span id=\"__span-0-42\"><a id=\"__codelineno-0-42\" name=\"__codelineno-0-42\" href=\"#__codelineno-0-42\"></a><span class=\"w\">            </span><span class=\"nt\">cpu</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s\">&quot;500m&quot;</span>\n</span><span id=\"__span-0-43\"><a id=\"__codelineno-0-43\" name=\"__codelineno-0-43\" href=\"#__codelineno-0-43\"></a><span class=\"w\">    </span><span class=\"nt\">volumes</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-44\"><a id=\"__codelineno-0-44\" name=\"__codelineno-0-44\" href=\"#__codelineno-0-44\"></a><span class=\"w\">        </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">data</span>\n</span><span id=\"__span-0-45\"><a id=\"__codelineno-0-45\" name=\"__codelineno-0-45\" href=\"#__codelineno-0-45\"></a><span class=\"w\">        </span><span class=\"nt\">persistentVolumeClaim</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-46\"><a id=\"__codelineno-0-46\" name=\"__codelineno-0-46\" href=\"#__codelineno-0-46\"></a><span class=\"w\">            </span><span class=\"nt\">claimName</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">freshrss-data</span>\n</span><span id=\"__span-0-47\"><a id=\"__codelineno-0-47\" name=\"__codelineno-0-47\" href=\"#__codelineno-0-47\"></a><span class=\"w\">        </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">extensions</span>\n</span><span id=\"__span-0-48\"><a id=\"__codelineno-0-48\" name=\"__codelineno-0-48\" href=\"#__codelineno-0-48\"></a><span class=\"w\">        </span><span class=\"nt\">persistentVolumeClaim</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-49\"><a id=\"__codelineno-0-49\" name=\"__codelineno-0-49\" href=\"#__codelineno-0-49\"></a><span class=\"w\">            </span><span class=\"nt\">claimName</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">freshrss-extensions</span>\n</span><span id=\"__span-0-50\"><a id=\"__codelineno-0-50\" name=\"__codelineno-0-50\" href=\"#__codelineno-0-50\"></a>\n</span><span id=\"__span-0-51\"><a id=\"__codelineno-0-51\" name=\"__codelineno-0-51\" href=\"#__codelineno-0-51\"></a><span class=\"nn\">---</span>\n</span><span id=\"__span-0-52\"><a id=\"__codelineno-0-52\" name=\"__codelineno-0-52\" href=\"#__codelineno-0-52\"></a><span class=\"c1\"># ingress.yaml</span>\n</span><span id=\"__span-0-53\"><a id=\"__codelineno-0-53\" name=\"__codelineno-0-53\" href=\"#__codelineno-0-53\"></a>\n</span><span id=\"__span-0-54\"><a id=\"__codelineno-0-54\" name=\"__codelineno-0-54\" href=\"#__codelineno-0-54\"></a><span class=\"nt\">apiVersion</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">networking.k8s.io/v1</span>\n</span><span id=\"__span-0-55\"><a id=\"__codelineno-0-55\" name=\"__codelineno-0-55\" href=\"#__codelineno-0-55\"></a><span class=\"nt\">kind</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">Ingress</span>\n</span><span id=\"__span-0-56\"><a id=\"__codelineno-0-56\" name=\"__codelineno-0-56\" href=\"#__codelineno-0-56\"></a><span class=\"nt\">metadata</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-57\"><a id=\"__codelineno-0-57\" name=\"__codelineno-0-57\" href=\"#__codelineno-0-57\"></a><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">freshrss</span>\n</span><span id=\"__span-0-58\"><a id=\"__codelineno-0-58\" name=\"__codelineno-0-58\" href=\"#__codelineno-0-58\"></a><span class=\"nt\">namespace</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">freshrss</span>\n</span><span id=\"__span-0-59\"><a id=\"__codelineno-0-59\" name=\"__codelineno-0-59\" href=\"#__codelineno-0-59\"></a><span class=\"nt\">annotations</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-60\"><a id=\"__codelineno-0-60\" name=\"__codelineno-0-60\" href=\"#__codelineno-0-60\"></a><span class=\"w\">    </span><span class=\"nt\">traefik.ingress.kubernetes.io/router.entrypoints</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">web</span>\n</span><span id=\"__span-0-61\"><a id=\"__codelineno-0-61\" name=\"__codelineno-0-61\" href=\"#__codelineno-0-61\"></a><span class=\"w\">    </span><span class=\"nt\">traefik.ingress.kubernetes.io/router.middlewares</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s\">&quot;freshrss-strip-prefix@kubernetescrd&quot;</span>\n</span><span id=\"__span-0-62\"><a id=\"__codelineno-0-62\" name=\"__codelineno-0-62\" href=\"#__codelineno-0-62\"></a><span class=\"w\">    </span><span class=\"nt\">traefik.ingress.kubernetes.io/router.tls</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s\">&quot;false&quot;</span>\n</span><span id=\"__span-0-63\"><a id=\"__codelineno-0-63\" name=\"__codelineno-0-63\" href=\"#__codelineno-0-63\"></a><span class=\"nt\">spec</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-64\"><a id=\"__codelineno-0-64\" name=\"__codelineno-0-64\" href=\"#__codelineno-0-64\"></a><span class=\"nt\">rules</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-65\"><a id=\"__codelineno-0-65\" name=\"__codelineno-0-65\" href=\"#__codelineno-0-65\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">host</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">tyr.local</span>\n</span><span id=\"__span-0-66\"><a id=\"__codelineno-0-66\" name=\"__codelineno-0-66\" href=\"#__codelineno-0-66\"></a><span class=\"w\">    </span><span class=\"nt\">http</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-67\"><a id=\"__codelineno-0-67\" name=\"__codelineno-0-67\" href=\"#__codelineno-0-67\"></a><span class=\"w\">        </span><span class=\"nt\">paths</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-68\"><a id=\"__codelineno-0-68\" name=\"__codelineno-0-68\" href=\"#__codelineno-0-68\"></a><span class=\"w\">        </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">path</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">/rss</span>\n</span><span id=\"__span-0-69\"><a id=\"__codelineno-0-69\" name=\"__codelineno-0-69\" href=\"#__codelineno-0-69\"></a><span class=\"w\">            </span><span class=\"l l-Scalar l-Scalar-Plain\">pathType</span><span class=\"p p-Indicator\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">Prefix</span>\n</span><span id=\"__span-0-70\"><a id=\"__codelineno-0-70\" name=\"__codelineno-0-70\" href=\"#__codelineno-0-70\"></a><span class=\"w\">            </span><span class=\"l l-Scalar l-Scalar-Plain\">backend</span><span class=\"p p-Indicator\">:</span>\n</span><span id=\"__span-0-71\"><a id=\"__codelineno-0-71\" name=\"__codelineno-0-71\" href=\"#__codelineno-0-71\"></a><span class=\"w\">            </span><span class=\"nt\">service</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-72\"><a id=\"__codelineno-0-72\" name=\"__codelineno-0-72\" href=\"#__codelineno-0-72\"></a><span class=\"w\">                </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">freshrss</span>\n</span><span id=\"__span-0-73\"><a id=\"__codelineno-0-73\" name=\"__codelineno-0-73\" href=\"#__codelineno-0-73\"></a><span class=\"w\">                </span><span class=\"nt\">port</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-74\"><a id=\"__codelineno-0-74\" name=\"__codelineno-0-74\" href=\"#__codelineno-0-74\"></a><span class=\"w\">                </span><span class=\"nt\">number</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">80</span>\n</span><span id=\"__span-0-75\"><a id=\"__codelineno-0-75\" name=\"__codelineno-0-75\" href=\"#__codelineno-0-75\"></a>\n</span><span id=\"__span-0-76\"><a id=\"__codelineno-0-76\" name=\"__codelineno-0-76\" href=\"#__codelineno-0-76\"></a><span class=\"nn\">---</span>\n</span><span id=\"__span-0-77\"><a id=\"__codelineno-0-77\" name=\"__codelineno-0-77\" href=\"#__codelineno-0-77\"></a><span class=\"c1\"># middleware.yaml</span>\n</span><span id=\"__span-0-78\"><a id=\"__codelineno-0-78\" name=\"__codelineno-0-78\" href=\"#__codelineno-0-78\"></a>\n</span><span id=\"__span-0-79\"><a id=\"__codelineno-0-79\" name=\"__codelineno-0-79\" href=\"#__codelineno-0-79\"></a><span class=\"nt\">apiVersion</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">traefik.io/v1alpha1</span>\n</span><span id=\"__span-0-80\"><a id=\"__codelineno-0-80\" name=\"__codelineno-0-80\" href=\"#__codelineno-0-80\"></a><span class=\"nt\">kind</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">Middleware</span>\n</span><span id=\"__span-0-81\"><a id=\"__codelineno-0-81\" name=\"__codelineno-0-81\" href=\"#__codelineno-0-81\"></a><span class=\"nt\">metadata</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-82\"><a id=\"__codelineno-0-82\" name=\"__codelineno-0-82\" href=\"#__codelineno-0-82\"></a><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">strip-prefix</span>\n</span><span id=\"__span-0-83\"><a id=\"__codelineno-0-83\" name=\"__codelineno-0-83\" href=\"#__codelineno-0-83\"></a><span class=\"nt\">namespace</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">freshrss</span>\n</span><span id=\"__span-0-84\"><a id=\"__codelineno-0-84\" name=\"__codelineno-0-84\" href=\"#__codelineno-0-84\"></a><span class=\"nt\">spec</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-85\"><a id=\"__codelineno-0-85\" name=\"__codelineno-0-85\" href=\"#__codelineno-0-85\"></a><span class=\"nt\">stripPrefix</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-86\"><a id=\"__codelineno-0-86\" name=\"__codelineno-0-86\" href=\"#__codelineno-0-86\"></a><span class=\"w\">    </span><span class=\"nt\">prefixes</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-87\"><a id=\"__codelineno-0-87\" name=\"__codelineno-0-87\" href=\"#__codelineno-0-87\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">/rss</span>\n</span><span id=\"__span-0-88\"><a id=\"__codelineno-0-88\" name=\"__codelineno-0-88\" href=\"#__codelineno-0-88\"></a><span class=\"w\">    </span><span class=\"nt\">forceSlash</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">true</span>\n</span><span id=\"__span-0-89\"><a id=\"__codelineno-0-89\" name=\"__codelineno-0-89\" href=\"#__codelineno-0-89\"></a>\n</span><span id=\"__span-0-90\"><a id=\"__codelineno-0-90\" name=\"__codelineno-0-90\" href=\"#__codelineno-0-90\"></a><span class=\"nn\">---</span>\n</span><span id=\"__span-0-91\"><a id=\"__codelineno-0-91\" name=\"__codelineno-0-91\" href=\"#__codelineno-0-91\"></a><span class=\"c1\"># namespace.yaml</span>\n</span><span id=\"__span-0-92\"><a id=\"__codelineno-0-92\" name=\"__codelineno-0-92\" href=\"#__codelineno-0-92\"></a>\n</span><span id=\"__span-0-93\"><a id=\"__codelineno-0-93\" name=\"__codelineno-0-93\" href=\"#__codelineno-0-93\"></a><span class=\"nt\">apiVersion</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">v1</span>\n</span><span id=\"__span-0-94\"><a id=\"__codelineno-0-94\" name=\"__codelineno-0-94\" href=\"#__codelineno-0-94\"></a><span class=\"nt\">kind</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">Namespace</span>\n</span><span id=\"__span-0-95\"><a id=\"__codelineno-0-95\" name=\"__codelineno-0-95\" href=\"#__codelineno-0-95\"></a><span class=\"nt\">metadata</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-96\"><a id=\"__codelineno-0-96\" name=\"__codelineno-0-96\" href=\"#__codelineno-0-96\"></a><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">freshrss</span>\n</span><span id=\"__span-0-97\"><a id=\"__codelineno-0-97\" name=\"__codelineno-0-97\" href=\"#__codelineno-0-97\"></a>\n</span><span id=\"__span-0-98\"><a id=\"__codelineno-0-98\" name=\"__codelineno-0-98\" href=\"#__codelineno-0-98\"></a><span class=\"nn\">---</span>\n</span><span id=\"__span-0-99\"><a id=\"__codelineno-0-99\" name=\"__codelineno-0-99\" href=\"#__codelineno-0-99\"></a><span class=\"c1\"># pvc.yaml</span>\n</span><span id=\"__span-0-100\"><a id=\"__codelineno-0-100\" name=\"__codelineno-0-100\" href=\"#__codelineno-0-100\"></a>\n</span><span id=\"__span-0-101\"><a id=\"__codelineno-0-101\" name=\"__codelineno-0-101\" href=\"#__codelineno-0-101\"></a><span class=\"nt\">apiVersion</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">v1</span>\n</span><span id=\"__span-0-102\"><a id=\"__codelineno-0-102\" name=\"__codelineno-0-102\" href=\"#__codelineno-0-102\"></a><span class=\"nt\">kind</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">PersistentVolumeClaim</span>\n</span><span id=\"__span-0-103\"><a id=\"__codelineno-0-103\" name=\"__codelineno-0-103\" href=\"#__codelineno-0-103\"></a><span class=\"nt\">metadata</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-104\"><a id=\"__codelineno-0-104\" name=\"__codelineno-0-104\" href=\"#__codelineno-0-104\"></a><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">freshrss-data</span>\n</span><span id=\"__span-0-105\"><a id=\"__codelineno-0-105\" name=\"__codelineno-0-105\" href=\"#__codelineno-0-105\"></a><span class=\"nt\">namespace</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">freshrss</span>\n</span><span id=\"__span-0-106\"><a id=\"__codelineno-0-106\" name=\"__codelineno-0-106\" href=\"#__codelineno-0-106\"></a><span class=\"nt\">spec</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-107\"><a id=\"__codelineno-0-107\" name=\"__codelineno-0-107\" href=\"#__codelineno-0-107\"></a><span class=\"nt\">accessModes</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-108\"><a id=\"__codelineno-0-108\" name=\"__codelineno-0-108\" href=\"#__codelineno-0-108\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">ReadWriteOnce</span>\n</span><span id=\"__span-0-109\"><a id=\"__codelineno-0-109\" name=\"__codelineno-0-109\" href=\"#__codelineno-0-109\"></a><span class=\"nt\">resources</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-110\"><a id=\"__codelineno-0-110\" name=\"__codelineno-0-110\" href=\"#__codelineno-0-110\"></a><span class=\"w\">    </span><span class=\"nt\">requests</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-111\"><a id=\"__codelineno-0-111\" name=\"__codelineno-0-111\" href=\"#__codelineno-0-111\"></a><span class=\"w\">    </span><span class=\"nt\">storage</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">1Gi</span>\n</span><span id=\"__span-0-112\"><a id=\"__codelineno-0-112\" name=\"__codelineno-0-112\" href=\"#__codelineno-0-112\"></a><span class=\"nn\">---</span>\n</span><span id=\"__span-0-113\"><a id=\"__codelineno-0-113\" name=\"__codelineno-0-113\" href=\"#__codelineno-0-113\"></a><span class=\"nt\">apiVersion</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">v1</span>\n</span><span id=\"__span-0-114\"><a id=\"__codelineno-0-114\" name=\"__codelineno-0-114\" href=\"#__codelineno-0-114\"></a><span class=\"nt\">kind</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">PersistentVolumeClaim</span>\n</span><span id=\"__span-0-115\"><a id=\"__codelineno-0-115\" name=\"__codelineno-0-115\" href=\"#__codelineno-0-115\"></a><span class=\"nt\">metadata</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-116\"><a id=\"__codelineno-0-116\" name=\"__codelineno-0-116\" href=\"#__codelineno-0-116\"></a><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">freshrss-extensions</span>\n</span><span id=\"__span-0-117\"><a id=\"__codelineno-0-117\" name=\"__codelineno-0-117\" href=\"#__codelineno-0-117\"></a><span class=\"nt\">namespace</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">freshrss</span>\n</span><span id=\"__span-0-118\"><a id=\"__codelineno-0-118\" name=\"__codelineno-0-118\" href=\"#__codelineno-0-118\"></a><span class=\"nt\">spec</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-119\"><a id=\"__codelineno-0-119\" name=\"__codelineno-0-119\" href=\"#__codelineno-0-119\"></a><span class=\"nt\">accessModes</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-120\"><a id=\"__codelineno-0-120\" name=\"__codelineno-0-120\" href=\"#__codelineno-0-120\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">ReadWriteOnce</span>\n</span><span id=\"__span-0-121\"><a id=\"__codelineno-0-121\" name=\"__codelineno-0-121\" href=\"#__codelineno-0-121\"></a><span class=\"nt\">resources</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-122\"><a id=\"__codelineno-0-122\" name=\"__codelineno-0-122\" href=\"#__codelineno-0-122\"></a><span class=\"w\">    </span><span class=\"nt\">requests</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-123\"><a id=\"__codelineno-0-123\" name=\"__codelineno-0-123\" href=\"#__codelineno-0-123\"></a><span class=\"w\">    </span><span class=\"nt\">storage</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">100Mi</span>\n</span><span id=\"__span-0-124\"><a id=\"__codelineno-0-124\" name=\"__codelineno-0-124\" href=\"#__codelineno-0-124\"></a>\n</span><span id=\"__span-0-125\"><a id=\"__codelineno-0-125\" name=\"__codelineno-0-125\" href=\"#__codelineno-0-125\"></a><span class=\"nn\">---</span>\n</span><span id=\"__span-0-126\"><a id=\"__codelineno-0-126\" name=\"__codelineno-0-126\" href=\"#__codelineno-0-126\"></a><span class=\"c1\"># service.yaml</span>\n</span><span id=\"__span-0-127\"><a id=\"__codelineno-0-127\" name=\"__codelineno-0-127\" href=\"#__codelineno-0-127\"></a>\n</span><span id=\"__span-0-128\"><a id=\"__codelineno-0-128\" name=\"__codelineno-0-128\" href=\"#__codelineno-0-128\"></a><span class=\"nt\">apiVersion</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">v1</span>\n</span><span id=\"__span-0-129\"><a id=\"__codelineno-0-129\" name=\"__codelineno-0-129\" href=\"#__codelineno-0-129\"></a><span class=\"nt\">kind</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">Service</span>\n</span><span id=\"__span-0-130\"><a id=\"__codelineno-0-130\" name=\"__codelineno-0-130\" href=\"#__codelineno-0-130\"></a><span class=\"nt\">metadata</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-131\"><a id=\"__codelineno-0-131\" name=\"__codelineno-0-131\" href=\"#__codelineno-0-131\"></a><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">freshrss</span>\n</span><span id=\"__span-0-132\"><a id=\"__codelineno-0-132\" name=\"__codelineno-0-132\" href=\"#__codelineno-0-132\"></a><span class=\"nt\">namespace</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">freshrss</span>\n</span><span id=\"__span-0-133\"><a id=\"__codelineno-0-133\" name=\"__codelineno-0-133\" href=\"#__codelineno-0-133\"></a><span class=\"nt\">spec</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-134\"><a id=\"__codelineno-0-134\" name=\"__codelineno-0-134\" href=\"#__codelineno-0-134\"></a><span class=\"nt\">selector</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-135\"><a id=\"__codelineno-0-135\" name=\"__codelineno-0-135\" href=\"#__codelineno-0-135\"></a><span class=\"w\">    </span><span class=\"nt\">app</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">freshrss</span>\n</span><span id=\"__span-0-136\"><a id=\"__codelineno-0-136\" name=\"__codelineno-0-136\" href=\"#__codelineno-0-136\"></a><span class=\"nt\">ports</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-137\"><a id=\"__codelineno-0-137\" name=\"__codelineno-0-137\" href=\"#__codelineno-0-137\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">port</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">80</span>\n</span><span id=\"__span-0-138\"><a id=\"__codelineno-0-138\" name=\"__codelineno-0-138\" href=\"#__codelineno-0-138\"></a><span class=\"w\">    </span><span class=\"nt\">targetPort</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">80</span>\n</span><span id=\"__span-0-139\"><a id=\"__codelineno-0-139\" name=\"__codelineno-0-139\" href=\"#__codelineno-0-139\"></a><span class=\"nt\">type</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">ClusterIP</span>\n</span></code></pre></div>\n</details>\n<h2 id=\"atuin\">Atuin</h2>\n<p>Again, I just asked Cursor to generate the Kubernetes manifests after linking to the <a href=\"https://docs.atuin.sh/self-hosting/kubernetes/\">self-hosting</a> docs. </p>\n<p>One issue I had here, was that I want to commit the manifests to git, but Atuin requires a password to communicate with the local Postgres database. </p>\n<div class=\"language-yaml highlight\"><span class=\"filename\">secrets.yaml</span><pre><span></span><code><span id=\"__span-1-1\"><a id=\"__codelineno-1-1\" name=\"__codelineno-1-1\" href=\"#__codelineno-1-1\"></a><span class=\"nt\">apiVersion</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">v1</span>\n</span><span id=\"__span-1-2\"><a id=\"__codelineno-1-2\" name=\"__codelineno-1-2\" href=\"#__codelineno-1-2\"></a><span class=\"nt\">kind</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">Secret</span>\n</span><span id=\"__span-1-3\"><a id=\"__codelineno-1-3\" name=\"__codelineno-1-3\" href=\"#__codelineno-1-3\"></a><span class=\"nt\">metadata</span><span class=\"p\">:</span>\n</span><span id=\"__span-1-4\"><a id=\"__codelineno-1-4\" name=\"__codelineno-1-4\" href=\"#__codelineno-1-4\"></a><span class=\"w\">  </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin-secrets</span>\n</span><span id=\"__span-1-5\"><a id=\"__codelineno-1-5\" name=\"__codelineno-1-5\" href=\"#__codelineno-1-5\"></a><span class=\"nt\">type</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">Opaque</span>\n</span><span id=\"__span-1-6\"><a id=\"__codelineno-1-6\" name=\"__codelineno-1-6\" href=\"#__codelineno-1-6\"></a><span class=\"nt\">stringData</span><span class=\"p\">:</span>\n</span><span id=\"__span-1-7\"><a id=\"__codelineno-1-7\" name=\"__codelineno-1-7\" href=\"#__codelineno-1-7\"></a><span class=\"w\">  </span><span class=\"nt\">ATUIN_DB_USERNAME</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin</span>\n</span><span id=\"__span-1-8\"><a id=\"__codelineno-1-8\" name=\"__codelineno-1-8\" href=\"#__codelineno-1-8\"></a><span class=\"w\">  </span><span class=\"nt\">ATUIN_DB_PASSWORD</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">seriously-insecure</span>\n</span><span id=\"__span-1-9\"><a id=\"__codelineno-1-9\" name=\"__codelineno-1-9\" href=\"#__codelineno-1-9\"></a><span class=\"w\">  </span><span class=\"nt\">ATUIN_HOST</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s\">&quot;127.0.0.1&quot;</span>\n</span><span id=\"__span-1-10\"><a id=\"__codelineno-1-10\" name=\"__codelineno-1-10\" href=\"#__codelineno-1-10\"></a><span class=\"w\">  </span><span class=\"nt\">ATUIN_PORT</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s\">&quot;8888&quot;</span>\n</span><span id=\"__span-1-11\"><a id=\"__codelineno-1-11\" name=\"__codelineno-1-11\" href=\"#__codelineno-1-11\"></a><span class=\"w\">  </span><span class=\"nt\">ATUIN_OPEN_REGISTRATION</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s\">&quot;true&quot;</span>\n</span><span id=\"__span-1-12\"><a id=\"__codelineno-1-12\" name=\"__codelineno-1-12\" href=\"#__codelineno-1-12\"></a><span class=\"w\">  </span><span class=\"nt\">ATUIN_DB_URI</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s\">&quot;postgres://atuin:seriously-insecure@postgres/atuin&quot;</span>\n</span><span id=\"__span-1-13\"><a id=\"__codelineno-1-13\" name=\"__codelineno-1-13\" href=\"#__codelineno-1-13\"></a><span class=\"nt\">immutable</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">true</span>\n</span></code></pre></div>\n<p>Since both services are local and not exposed to the internet, there is probably no harm in commiting something like this. </p>\n<p>But I had a discussion about this problem with my smart, very kubernetes savvy friend, Lasse. So I wanted to find some solution to this that did not commit any deadly sins, such as committing a secret in plaintext. </p>\n<p>Again, I asked Cursor for suggestions, prompting it to not make any changes to my files, just list out options. </p>\n<p>Here is the full list of suggestions:</p>\n<details class=\"note\">\n<summary>List some ways I can add secrets here without leaking it in git</summary>\n<ol>\n<li>\n<p>Using Sealed Secrets:</p>\n<div class=\"language-sh highlight\"><pre><span></span><code><span id=\"__span-2-1\"><a id=\"__codelineno-2-1\" name=\"__codelineno-2-1\" href=\"#__codelineno-2-1\"></a><span class=\"c1\"># Install sealed-secrets controller</span>\n</span><span id=\"__span-2-2\"><a id=\"__codelineno-2-2\" name=\"__codelineno-2-2\" href=\"#__codelineno-2-2\"></a>helm<span class=\"w\"> </span>install<span class=\"w\"> </span>sealed-secrets<span class=\"w\"> </span>sealed-secrets/sealed-secrets\n</span><span id=\"__span-2-3\"><a id=\"__codelineno-2-3\" name=\"__codelineno-2-3\" href=\"#__codelineno-2-3\"></a><span class=\"c1\"># Encrypt your secrets</span>\n</span><span id=\"__span-2-4\"><a id=\"__codelineno-2-4\" name=\"__codelineno-2-4\" href=\"#__codelineno-2-4\"></a>kubeseal<span class=\"w\"> </span>&lt;<span class=\"w\"> </span>atuin/secrets.yaml<span class=\"w\"> </span>&gt;<span class=\"w\"> </span>atuin/sealed-secrets.yaml\n</span></code></pre></div>\n<p>This lets you safely commit encrypted secrets that only your cluster can decrypt.</p>\n</li>\n<li>\n<p>Using External Secrets Operator with a secrets manager:</p>\n<div class=\"language-yaml highlight\"><pre><span></span><code><span id=\"__span-3-1\"><a id=\"__codelineno-3-1\" name=\"__codelineno-3-1\" href=\"#__codelineno-3-1\"></a><span class=\"nt\">apiVersion</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">external-secrets.io/v1beta1</span>\n</span><span id=\"__span-3-2\"><a id=\"__codelineno-3-2\" name=\"__codelineno-3-2\" href=\"#__codelineno-3-2\"></a><span class=\"nt\">kind</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">ExternalSecret</span>\n</span><span id=\"__span-3-3\"><a id=\"__codelineno-3-3\" name=\"__codelineno-3-3\" href=\"#__codelineno-3-3\"></a><span class=\"nt\">metadata</span><span class=\"p\">:</span>\n</span><span id=\"__span-3-4\"><a id=\"__codelineno-3-4\" name=\"__codelineno-3-4\" href=\"#__codelineno-3-4\"></a><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin-secrets</span>\n</span><span id=\"__span-3-5\"><a id=\"__codelineno-3-5\" name=\"__codelineno-3-5\" href=\"#__codelineno-3-5\"></a><span class=\"nt\">namespace</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin</span>\n</span><span id=\"__span-3-6\"><a id=\"__codelineno-3-6\" name=\"__codelineno-3-6\" href=\"#__codelineno-3-6\"></a><span class=\"nt\">spec</span><span class=\"p\">:</span>\n</span><span id=\"__span-3-7\"><a id=\"__codelineno-3-7\" name=\"__codelineno-3-7\" href=\"#__codelineno-3-7\"></a><span class=\"nt\">refreshInterval</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">1h</span>\n</span><span id=\"__span-3-8\"><a id=\"__codelineno-3-8\" name=\"__codelineno-3-8\" href=\"#__codelineno-3-8\"></a><span class=\"nt\">secretStoreRef</span><span class=\"p\">:</span>\n</span><span id=\"__span-3-9\"><a id=\"__codelineno-3-9\" name=\"__codelineno-3-9\" href=\"#__codelineno-3-9\"></a><span class=\"w\">    </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">vault-backend</span>\n</span><span id=\"__span-3-10\"><a id=\"__codelineno-3-10\" name=\"__codelineno-3-10\" href=\"#__codelineno-3-10\"></a><span class=\"w\">    </span><span class=\"nt\">kind</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">ClusterSecretStore</span>\n</span><span id=\"__span-3-11\"><a id=\"__codelineno-3-11\" name=\"__codelineno-3-11\" href=\"#__codelineno-3-11\"></a><span class=\"nt\">target</span><span class=\"p\">:</span>\n</span><span id=\"__span-3-12\"><a id=\"__codelineno-3-12\" name=\"__codelineno-3-12\" href=\"#__codelineno-3-12\"></a><span class=\"w\">    </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin-secrets</span>\n</span><span id=\"__span-3-13\"><a id=\"__codelineno-3-13\" name=\"__codelineno-3-13\" href=\"#__codelineno-3-13\"></a><span class=\"nt\">data</span><span class=\"p\">:</span>\n</span><span id=\"__span-3-14\"><a id=\"__codelineno-3-14\" name=\"__codelineno-3-14\" href=\"#__codelineno-3-14\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">secretKey</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">ATUIN_DB_PASSWORD</span>\n</span><span id=\"__span-3-15\"><a id=\"__codelineno-3-15\" name=\"__codelineno-3-15\" href=\"#__codelineno-3-15\"></a><span class=\"w\">    </span><span class=\"nt\">remoteRef</span><span class=\"p\">:</span>\n</span><span id=\"__span-3-16\"><a id=\"__codelineno-3-16\" name=\"__codelineno-3-16\" href=\"#__codelineno-3-16\"></a><span class=\"w\">        </span><span class=\"nt\">key</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin/db-password</span>\n</span></code></pre></div>\n<p>This fetches secrets from services like HashiCorp Vault, AWS Secrets Manager, or Azure Key Vault.</p>\n</li>\n<li>\n<p>Using Kustomize with secret generators:\n    <div class=\"language-yaml highlight\"><pre><span></span><code><span id=\"__span-4-1\"><a id=\"__codelineno-4-1\" name=\"__codelineno-4-1\" href=\"#__codelineno-4-1\"></a><span class=\"c1\"># kustomization.yaml</span>\n</span><span id=\"__span-4-2\"><a id=\"__codelineno-4-2\" name=\"__codelineno-4-2\" href=\"#__codelineno-4-2\"></a><span class=\"nt\">secretGenerator</span><span class=\"p\">:</span>\n</span><span id=\"__span-4-3\"><a id=\"__codelineno-4-3\" name=\"__codelineno-4-3\" href=\"#__codelineno-4-3\"></a><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin-secrets</span>\n</span><span id=\"__span-4-4\"><a id=\"__codelineno-4-4\" name=\"__codelineno-4-4\" href=\"#__codelineno-4-4\"></a><span class=\"nt\">namespace</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin</span>\n</span><span id=\"__span-4-5\"><a id=\"__codelineno-4-5\" name=\"__codelineno-4-5\" href=\"#__codelineno-4-5\"></a><span class=\"nt\">envs</span><span class=\"p\">:</span>\n</span><span id=\"__span-4-6\"><a id=\"__codelineno-4-6\" name=\"__codelineno-4-6\" href=\"#__codelineno-4-6\"></a><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">.env</span><span class=\"w\"> </span><span class=\"c1\"># This file is not committed</span>\n</span></code></pre></div>\n    This generates secrets from local files that aren't committed.</p>\n</li>\n<li>\n<p>Using environment variables with a CI/CD pipeline:\n    <div class=\"language-yaml highlight\"><pre><span></span><code><span id=\"__span-5-1\"><a id=\"__codelineno-5-1\" name=\"__codelineno-5-1\" href=\"#__codelineno-5-1\"></a><span class=\"nt\">apiVersion</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">v1</span>\n</span><span id=\"__span-5-2\"><a id=\"__codelineno-5-2\" name=\"__codelineno-5-2\" href=\"#__codelineno-5-2\"></a><span class=\"nt\">kind</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">Secret</span>\n</span><span id=\"__span-5-3\"><a id=\"__codelineno-5-3\" name=\"__codelineno-5-3\" href=\"#__codelineno-5-3\"></a><span class=\"nt\">metadata</span><span class=\"p\">:</span>\n</span><span id=\"__span-5-4\"><a id=\"__codelineno-5-4\" name=\"__codelineno-5-4\" href=\"#__codelineno-5-4\"></a><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin-secrets</span>\n</span><span id=\"__span-5-5\"><a id=\"__codelineno-5-5\" name=\"__codelineno-5-5\" href=\"#__codelineno-5-5\"></a><span class=\"nt\">namespace</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin</span>\n</span><span id=\"__span-5-6\"><a id=\"__codelineno-5-6\" name=\"__codelineno-5-6\" href=\"#__codelineno-5-6\"></a><span class=\"nt\">stringData</span><span class=\"p\">:</span>\n</span><span id=\"__span-5-7\"><a id=\"__codelineno-5-7\" name=\"__codelineno-5-7\" href=\"#__codelineno-5-7\"></a><span class=\"nt\">ATUIN_DB_PASSWORD</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">${ATUIN_DB_PASSWORD}</span>\n</span><span id=\"__span-5-8\"><a id=\"__codelineno-5-8\" name=\"__codelineno-5-8\" href=\"#__codelineno-5-8\"></a><span class=\"nt\">ATUIN_DB_URI</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s\">&quot;postgres://atuin:${ATUIN_DB_PASSWORD}@postgres/atuin&quot;</span>\n</span></code></pre></div>\n    Your CI/CD system (like GitHub Actions) replaces the variables before applying.</p>\n</li>\n<li>\n<p>Using SOPS (Secrets OPerationS):\n    <div class=\"language-yaml highlight\"><pre><span></span><code><span id=\"__span-6-1\"><a id=\"__codelineno-6-1\" name=\"__codelineno-6-1\" href=\"#__codelineno-6-1\"></a><span class=\"c1\"># Encrypt secrets</span>\n</span><span id=\"__span-6-2\"><a id=\"__codelineno-6-2\" name=\"__codelineno-6-2\" href=\"#__codelineno-6-2\"></a><span class=\"l l-Scalar l-Scalar-Plain\">sops -e -i atuin/secrets.yaml</span>\n</span><span id=\"__span-6-3\"><a id=\"__codelineno-6-3\" name=\"__codelineno-6-3\" href=\"#__codelineno-6-3\"></a>\n</span><span id=\"__span-6-4\"><a id=\"__codelineno-6-4\" name=\"__codelineno-6-4\" href=\"#__codelineno-6-4\"></a><span class=\"l l-Scalar l-Scalar-Plain\"># Decrypt when needed</span>\n</span><span id=\"__span-6-5\"><a id=\"__codelineno-6-5\" name=\"__codelineno-6-5\" href=\"#__codelineno-6-5\"></a><span class=\"l l-Scalar l-Scalar-Plain\">sops -d atuin/secrets.yaml | kubectl apply -f -</span>\n</span></code></pre></div>\n    This encrypts secrets with age, PGP, or cloud KMS keys.</p>\n</li>\n</ol>\n</details>\n<p>I chose to use <a href=\"https://github.com/bitnami-labs/sealed-secrets?tab=readme-ov-file#kubeseal\">Sealed Secrets</a>. It sounded by far the simplest, and I could sort-of understand what was going on:</p>\n<ul>\n<li>I install the SealedSecret helm chart on my cluster. This can encrypt and decrypt text using some fancy crypto magic. Basically a private/public key pair is generated and stored as Kubernetes secerets and these are used to encrypt <strong>my secrets</strong>.</li>\n<li>I install <code>kubeseal</code> on my local machine. It can communicate with the SealedSecret controller running in the cluster. </li>\n<li>I pass a local (uncommitted) kubernetes <code>Secret</code> to <code>kubeseal</code>, it encrypts it and I get back a <code>SealedSecret</code>. </li>\n<li>I can then store, apply and commit this <code>SealedSecret</code>. It will get unsealed when applied to my cluster (so my services can use it), but the unsealing only happens inside the cluster. My local manifest file is encrypted. </li>\n</ul>\n<p>Let's say I want to encrypt this Secret\n<div class=\"language-yaml highlight\"><span class=\"filename\">secrets.yaml</span><pre><span></span><code><span id=\"__span-7-1\"><a id=\"__codelineno-7-1\" name=\"__codelineno-7-1\" href=\"#__codelineno-7-1\"></a><span class=\"nt\">apiVersion</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">v1</span>\n</span><span id=\"__span-7-2\"><a id=\"__codelineno-7-2\" name=\"__codelineno-7-2\" href=\"#__codelineno-7-2\"></a><span class=\"nt\">kind</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">Secret</span>\n</span><span id=\"__span-7-3\"><a id=\"__codelineno-7-3\" name=\"__codelineno-7-3\" href=\"#__codelineno-7-3\"></a><span class=\"nt\">metadata</span><span class=\"p\">:</span>\n</span><span id=\"__span-7-4\"><a id=\"__codelineno-7-4\" name=\"__codelineno-7-4\" href=\"#__codelineno-7-4\"></a><span class=\"w\">  </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin-secrets</span>\n</span><span id=\"__span-7-5\"><a id=\"__codelineno-7-5\" name=\"__codelineno-7-5\" href=\"#__codelineno-7-5\"></a><span class=\"w\">  </span><span class=\"nt\">namespace</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin</span>\n</span><span id=\"__span-7-6\"><a id=\"__codelineno-7-6\" name=\"__codelineno-7-6\" href=\"#__codelineno-7-6\"></a><span class=\"nt\">type</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">Opaque</span>\n</span><span id=\"__span-7-7\"><a id=\"__codelineno-7-7\" name=\"__codelineno-7-7\" href=\"#__codelineno-7-7\"></a><span class=\"nt\">stringData</span><span class=\"p\">:</span>\n</span><span id=\"__span-7-8\"><a id=\"__codelineno-7-8\" name=\"__codelineno-7-8\" href=\"#__codelineno-7-8\"></a><span class=\"w\">  </span><span class=\"nt\">ATUIN_DB_USERNAME</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin</span>\n</span><span id=\"__span-7-9\"><a id=\"__codelineno-7-9\" name=\"__codelineno-7-9\" href=\"#__codelineno-7-9\"></a><span class=\"w\">  </span><span class=\"nt\">ATUIN_DB_PASSWORD</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s\">&quot;123&quot;</span>\n</span><span id=\"__span-7-10\"><a id=\"__codelineno-7-10\" name=\"__codelineno-7-10\" href=\"#__codelineno-7-10\"></a><span class=\"w\">  </span><span class=\"nt\">ATUIN_DB_URI</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s\">&quot;postgres://username:123@postgres/atuin&quot;</span><span class=\"w\"> </span><span class=\"c1\"># Match the password here</span>\n</span></code></pre></div></p>\n<p>I can run <code>kubeseal</code> to encrypt:\n<div class=\"language-sh highlight\"><pre><span></span><code><span id=\"__span-8-1\"><a id=\"__codelineno-8-1\" name=\"__codelineno-8-1\" href=\"#__codelineno-8-1\"></a>kubeseal<span class=\"w\"> </span>&lt;<span class=\"w\"> </span>secrets.yaml<span class=\"w\"> </span>&gt;<span class=\"w\"> </span>sealed-secrets.yaml\n</span></code></pre></div></p>\n<p>and I get back</p>\n<div class=\"language-yaml highlight\"><span class=\"filename\">sealed-secrets.yaml</span><pre><span></span><code><span id=\"__span-9-1\"><a id=\"__codelineno-9-1\" name=\"__codelineno-9-1\" href=\"#__codelineno-9-1\"></a><span class=\"nt\">apiVersion</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">bitnami.com/v1alpha1</span>\n</span><span id=\"__span-9-2\"><a id=\"__codelineno-9-2\" name=\"__codelineno-9-2\" href=\"#__codelineno-9-2\"></a><span class=\"nt\">kind</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">SealedSecret</span>\n</span><span id=\"__span-9-3\"><a id=\"__codelineno-9-3\" name=\"__codelineno-9-3\" href=\"#__codelineno-9-3\"></a><span class=\"nt\">metadata</span><span class=\"p\">:</span>\n</span><span id=\"__span-9-4\"><a id=\"__codelineno-9-4\" name=\"__codelineno-9-4\" href=\"#__codelineno-9-4\"></a><span class=\"w\">  </span><span class=\"nt\">creationTimestamp</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">null</span>\n</span><span id=\"__span-9-5\"><a id=\"__codelineno-9-5\" name=\"__codelineno-9-5\" href=\"#__codelineno-9-5\"></a><span class=\"w\">  </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin-secrets</span>\n</span><span id=\"__span-9-6\"><a id=\"__codelineno-9-6\" name=\"__codelineno-9-6\" href=\"#__codelineno-9-6\"></a><span class=\"w\">  </span><span class=\"nt\">namespace</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin</span>\n</span><span id=\"__span-9-7\"><a id=\"__codelineno-9-7\" name=\"__codelineno-9-7\" href=\"#__codelineno-9-7\"></a><span class=\"nt\">spec</span><span class=\"p\">:</span>\n</span><span id=\"__span-9-8\"><a id=\"__codelineno-9-8\" name=\"__codelineno-9-8\" href=\"#__codelineno-9-8\"></a><span class=\"w\">  </span><span class=\"nt\">encryptedData</span><span class=\"p\">:</span>\n</span><span id=\"__span-9-9\"><a id=\"__codelineno-9-9\" name=\"__codelineno-9-9\" href=\"#__codelineno-9-9\"></a><span class=\"w\">    </span><span class=\"nt\">ATUIN_DB_PASSWORD</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">AgBKfphBarMiNX8CIsvjAXqEtRp/Bq+a4y67k/M6bxMm1w/[TRUNCATED FOR SPACE]</span>\n</span><span id=\"__span-9-10\"><a id=\"__codelineno-9-10\" name=\"__codelineno-9-10\" href=\"#__codelineno-9-10\"></a><span class=\"w\">    </span><span class=\"nt\">ATUIN_DB_URI</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">AgCfm2AisGVBlMrOqPvMWOor0e0UXDruZnWVG3klrfSzbtZfrzYF4x[TRUNCATED FOR SPACE]</span>\n</span><span id=\"__span-9-11\"><a id=\"__codelineno-9-11\" name=\"__codelineno-9-11\" href=\"#__codelineno-9-11\"></a><span class=\"w\">    </span><span class=\"nt\">ATUIN_DB_USERNAME</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">AgAt8yDkKRjmvJtB4ecxOOcuEm1Zcoa8pX1UvtvwAAT4M18PN3JK[TRUNCATED FOR SPACE]</span>\n</span><span id=\"__span-9-12\"><a id=\"__codelineno-9-12\" name=\"__codelineno-9-12\" href=\"#__codelineno-9-12\"></a><span class=\"w\">  </span><span class=\"nt\">template</span><span class=\"p\">:</span>\n</span><span id=\"__span-9-13\"><a id=\"__codelineno-9-13\" name=\"__codelineno-9-13\" href=\"#__codelineno-9-13\"></a><span class=\"w\">    </span><span class=\"nt\">metadata</span><span class=\"p\">:</span>\n</span><span id=\"__span-9-14\"><a id=\"__codelineno-9-14\" name=\"__codelineno-9-14\" href=\"#__codelineno-9-14\"></a><span class=\"w\">      </span><span class=\"nt\">creationTimestamp</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">null</span>\n</span><span id=\"__span-9-15\"><a id=\"__codelineno-9-15\" name=\"__codelineno-9-15\" href=\"#__codelineno-9-15\"></a><span class=\"w\">      </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin-secrets</span>\n</span><span id=\"__span-9-16\"><a id=\"__codelineno-9-16\" name=\"__codelineno-9-16\" href=\"#__codelineno-9-16\"></a><span class=\"w\">      </span><span class=\"nt\">namespace</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin</span>\n</span><span id=\"__span-9-17\"><a id=\"__codelineno-9-17\" name=\"__codelineno-9-17\" href=\"#__codelineno-9-17\"></a><span class=\"w\">    </span><span class=\"nt\">type</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">Opaque</span>\n</span></code></pre></div>\n<p>Pretty cool! \nI have also backed up the Sealed Secrets private key in my 1Password. </p>\n<div class=\"language-sh highlight\"><pre><span></span><code><span id=\"__span-10-1\"><a id=\"__codelineno-10-1\" name=\"__codelineno-10-1\" href=\"#__codelineno-10-1\"></a>kubectl<span class=\"w\"> </span>get<span class=\"w\"> </span>secret<span class=\"w\"> </span>-n<span class=\"w\"> </span>kube-system<span class=\"w\"> </span>-l<span class=\"w\"> </span>sealedsecrets.bitnami.com/sealed-secrets-key<span class=\"w\"> </span>-o<span class=\"w\"> </span>yaml<span class=\"w\"> </span>&gt;<span class=\"w\"> </span>sealed-secrets-master.key\n</span></code></pre></div>\n<p>If my cluster suddenly catches fire, I can recreate my deployments in a new cluster by adding the key to that cluster\n<div class=\"language-sh highlight\"><pre><span></span><code><span id=\"__span-11-1\"><a id=\"__codelineno-11-1\" name=\"__codelineno-11-1\" href=\"#__codelineno-11-1\"></a>kubectl<span class=\"w\"> </span>apply<span class=\"w\"> </span>-f<span class=\"w\"> </span>sealed-secrets-master.key\n</span><span id=\"__span-11-2\"><a id=\"__codelineno-11-2\" name=\"__codelineno-11-2\" href=\"#__codelineno-11-2\"></a>kubectl<span class=\"w\"> </span>delete<span class=\"w\"> </span>pod<span class=\"w\"> </span>-n<span class=\"w\"> </span>kube-system<span class=\"w\"> </span>-l<span class=\"w\"> </span><span class=\"nv\">name</span><span class=\"o\">=</span>sealed-secrets-controller\n</span></code></pre></div></p>\n<p>Here is the complete manifest</p>\n<details class=\"note\">\n<summary>Complete manifest</summary>\n<div class=\"language-yaml highlight\"><pre><span></span><code><span id=\"__span-12-1\"><a id=\"__codelineno-12-1\" name=\"__codelineno-12-1\" href=\"#__codelineno-12-1\"></a><span class=\"c1\"># config.yaml</span>\n</span><span id=\"__span-12-2\"><a id=\"__codelineno-12-2\" name=\"__codelineno-12-2\" href=\"#__codelineno-12-2\"></a>\n</span><span id=\"__span-12-3\"><a id=\"__codelineno-12-3\" name=\"__codelineno-12-3\" href=\"#__codelineno-12-3\"></a><span class=\"nt\">apiVersion</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">v1</span>\n</span><span id=\"__span-12-4\"><a id=\"__codelineno-12-4\" name=\"__codelineno-12-4\" href=\"#__codelineno-12-4\"></a><span class=\"nt\">kind</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">ConfigMap</span>\n</span><span id=\"__span-12-5\"><a id=\"__codelineno-12-5\" name=\"__codelineno-12-5\" href=\"#__codelineno-12-5\"></a><span class=\"nt\">metadata</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-6\"><a id=\"__codelineno-12-6\" name=\"__codelineno-12-6\" href=\"#__codelineno-12-6\"></a><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin-config</span>\n</span><span id=\"__span-12-7\"><a id=\"__codelineno-12-7\" name=\"__codelineno-12-7\" href=\"#__codelineno-12-7\"></a><span class=\"nt\">namespace</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin</span>\n</span><span id=\"__span-12-8\"><a id=\"__codelineno-12-8\" name=\"__codelineno-12-8\" href=\"#__codelineno-12-8\"></a><span class=\"nt\">data</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-9\"><a id=\"__codelineno-12-9\" name=\"__codelineno-12-9\" href=\"#__codelineno-12-9\"></a><span class=\"nt\">ATUIN_HOST</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s\">&quot;0.0.0.0&quot;</span>\n</span><span id=\"__span-12-10\"><a id=\"__codelineno-12-10\" name=\"__codelineno-12-10\" href=\"#__codelineno-12-10\"></a><span class=\"nt\">ATUIN_PORT</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s\">&quot;8888&quot;</span>\n</span><span id=\"__span-12-11\"><a id=\"__codelineno-12-11\" name=\"__codelineno-12-11\" href=\"#__codelineno-12-11\"></a><span class=\"nt\">ATUIN_OPEN_REGISTRATION</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s\">&quot;true&quot;</span>\n</span><span id=\"__span-12-12\"><a id=\"__codelineno-12-12\" name=\"__codelineno-12-12\" href=\"#__codelineno-12-12\"></a>\n</span><span id=\"__span-12-13\"><a id=\"__codelineno-12-13\" name=\"__codelineno-12-13\" href=\"#__codelineno-12-13\"></a><span class=\"nn\">---</span>\n</span><span id=\"__span-12-14\"><a id=\"__codelineno-12-14\" name=\"__codelineno-12-14\" href=\"#__codelineno-12-14\"></a><span class=\"c1\"># deployment.yaml</span>\n</span><span id=\"__span-12-15\"><a id=\"__codelineno-12-15\" name=\"__codelineno-12-15\" href=\"#__codelineno-12-15\"></a>\n</span><span id=\"__span-12-16\"><a id=\"__codelineno-12-16\" name=\"__codelineno-12-16\" href=\"#__codelineno-12-16\"></a><span class=\"nn\">---</span>\n</span><span id=\"__span-12-17\"><a id=\"__codelineno-12-17\" name=\"__codelineno-12-17\" href=\"#__codelineno-12-17\"></a><span class=\"nt\">apiVersion</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">apps/v1</span>\n</span><span id=\"__span-12-18\"><a id=\"__codelineno-12-18\" name=\"__codelineno-12-18\" href=\"#__codelineno-12-18\"></a><span class=\"nt\">kind</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">Deployment</span>\n</span><span id=\"__span-12-19\"><a id=\"__codelineno-12-19\" name=\"__codelineno-12-19\" href=\"#__codelineno-12-19\"></a><span class=\"nt\">metadata</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-20\"><a id=\"__codelineno-12-20\" name=\"__codelineno-12-20\" href=\"#__codelineno-12-20\"></a><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">postgres</span>\n</span><span id=\"__span-12-21\"><a id=\"__codelineno-12-21\" name=\"__codelineno-12-21\" href=\"#__codelineno-12-21\"></a><span class=\"nt\">namespace</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin</span>\n</span><span id=\"__span-12-22\"><a id=\"__codelineno-12-22\" name=\"__codelineno-12-22\" href=\"#__codelineno-12-22\"></a><span class=\"nt\">spec</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-23\"><a id=\"__codelineno-12-23\" name=\"__codelineno-12-23\" href=\"#__codelineno-12-23\"></a><span class=\"nt\">replicas</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">1</span>\n</span><span id=\"__span-12-24\"><a id=\"__codelineno-12-24\" name=\"__codelineno-12-24\" href=\"#__codelineno-12-24\"></a><span class=\"nt\">strategy</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-25\"><a id=\"__codelineno-12-25\" name=\"__codelineno-12-25\" href=\"#__codelineno-12-25\"></a><span class=\"w\">    </span><span class=\"nt\">type</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">Recreate</span><span class=\"w\"> </span><span class=\"c1\"># Prevent data corruption by ensuring only one pod runs</span>\n</span><span id=\"__span-12-26\"><a id=\"__codelineno-12-26\" name=\"__codelineno-12-26\" href=\"#__codelineno-12-26\"></a><span class=\"nt\">selector</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-27\"><a id=\"__codelineno-12-27\" name=\"__codelineno-12-27\" href=\"#__codelineno-12-27\"></a><span class=\"w\">    </span><span class=\"nt\">matchLabels</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-28\"><a id=\"__codelineno-12-28\" name=\"__codelineno-12-28\" href=\"#__codelineno-12-28\"></a><span class=\"w\">    </span><span class=\"nt\">app</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">postgres</span>\n</span><span id=\"__span-12-29\"><a id=\"__codelineno-12-29\" name=\"__codelineno-12-29\" href=\"#__codelineno-12-29\"></a><span class=\"nt\">template</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-30\"><a id=\"__codelineno-12-30\" name=\"__codelineno-12-30\" href=\"#__codelineno-12-30\"></a><span class=\"w\">    </span><span class=\"nt\">metadata</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-31\"><a id=\"__codelineno-12-31\" name=\"__codelineno-12-31\" href=\"#__codelineno-12-31\"></a><span class=\"w\">    </span><span class=\"nt\">labels</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-32\"><a id=\"__codelineno-12-32\" name=\"__codelineno-12-32\" href=\"#__codelineno-12-32\"></a><span class=\"w\">        </span><span class=\"nt\">app</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">postgres</span>\n</span><span id=\"__span-12-33\"><a id=\"__codelineno-12-33\" name=\"__codelineno-12-33\" href=\"#__codelineno-12-33\"></a><span class=\"w\">    </span><span class=\"nt\">spec</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-34\"><a id=\"__codelineno-12-34\" name=\"__codelineno-12-34\" href=\"#__codelineno-12-34\"></a><span class=\"w\">    </span><span class=\"nt\">containers</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-35\"><a id=\"__codelineno-12-35\" name=\"__codelineno-12-35\" href=\"#__codelineno-12-35\"></a><span class=\"w\">        </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">postgresql</span>\n</span><span id=\"__span-12-36\"><a id=\"__codelineno-12-36\" name=\"__codelineno-12-36\" href=\"#__codelineno-12-36\"></a><span class=\"w\">        </span><span class=\"nt\">image</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">postgres:14</span>\n</span><span id=\"__span-12-37\"><a id=\"__codelineno-12-37\" name=\"__codelineno-12-37\" href=\"#__codelineno-12-37\"></a><span class=\"w\">        </span><span class=\"nt\">ports</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-38\"><a id=\"__codelineno-12-38\" name=\"__codelineno-12-38\" href=\"#__codelineno-12-38\"></a><span class=\"w\">            </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">containerPort</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">5432</span>\n</span><span id=\"__span-12-39\"><a id=\"__codelineno-12-39\" name=\"__codelineno-12-39\" href=\"#__codelineno-12-39\"></a><span class=\"w\">        </span><span class=\"nt\">env</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-40\"><a id=\"__codelineno-12-40\" name=\"__codelineno-12-40\" href=\"#__codelineno-12-40\"></a><span class=\"w\">            </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">POSTGRES_DB</span>\n</span><span id=\"__span-12-41\"><a id=\"__codelineno-12-41\" name=\"__codelineno-12-41\" href=\"#__codelineno-12-41\"></a><span class=\"w\">            </span><span class=\"nt\">value</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin</span>\n</span><span id=\"__span-12-42\"><a id=\"__codelineno-12-42\" name=\"__codelineno-12-42\" href=\"#__codelineno-12-42\"></a><span class=\"w\">            </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">POSTGRES_PASSWORD</span>\n</span><span id=\"__span-12-43\"><a id=\"__codelineno-12-43\" name=\"__codelineno-12-43\" href=\"#__codelineno-12-43\"></a><span class=\"w\">            </span><span class=\"nt\">valueFrom</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-44\"><a id=\"__codelineno-12-44\" name=\"__codelineno-12-44\" href=\"#__codelineno-12-44\"></a><span class=\"w\">                </span><span class=\"nt\">secretKeyRef</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-45\"><a id=\"__codelineno-12-45\" name=\"__codelineno-12-45\" href=\"#__codelineno-12-45\"></a><span class=\"w\">                </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin-secrets</span>\n</span><span id=\"__span-12-46\"><a id=\"__codelineno-12-46\" name=\"__codelineno-12-46\" href=\"#__codelineno-12-46\"></a><span class=\"w\">                </span><span class=\"nt\">key</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">ATUIN_DB_PASSWORD</span>\n</span><span id=\"__span-12-47\"><a id=\"__codelineno-12-47\" name=\"__codelineno-12-47\" href=\"#__codelineno-12-47\"></a><span class=\"w\">            </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">POSTGRES_USER</span>\n</span><span id=\"__span-12-48\"><a id=\"__codelineno-12-48\" name=\"__codelineno-12-48\" href=\"#__codelineno-12-48\"></a><span class=\"w\">            </span><span class=\"nt\">valueFrom</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-49\"><a id=\"__codelineno-12-49\" name=\"__codelineno-12-49\" href=\"#__codelineno-12-49\"></a><span class=\"w\">                </span><span class=\"nt\">secretKeyRef</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-50\"><a id=\"__codelineno-12-50\" name=\"__codelineno-12-50\" href=\"#__codelineno-12-50\"></a><span class=\"w\">                </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin-secrets</span>\n</span><span id=\"__span-12-51\"><a id=\"__codelineno-12-51\" name=\"__codelineno-12-51\" href=\"#__codelineno-12-51\"></a><span class=\"w\">                </span><span class=\"nt\">key</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">ATUIN_DB_USERNAME</span>\n</span><span id=\"__span-12-52\"><a id=\"__codelineno-12-52\" name=\"__codelineno-12-52\" href=\"#__codelineno-12-52\"></a><span class=\"w\">        </span><span class=\"nt\">lifecycle</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-53\"><a id=\"__codelineno-12-53\" name=\"__codelineno-12-53\" href=\"#__codelineno-12-53\"></a><span class=\"w\">            </span><span class=\"nt\">preStop</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-54\"><a id=\"__codelineno-12-54\" name=\"__codelineno-12-54\" href=\"#__codelineno-12-54\"></a><span class=\"w\">            </span><span class=\"nt\">exec</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-55\"><a id=\"__codelineno-12-55\" name=\"__codelineno-12-55\" href=\"#__codelineno-12-55\"></a><span class=\"w\">                </span><span class=\"nt\">command</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-56\"><a id=\"__codelineno-12-56\" name=\"__codelineno-12-56\" href=\"#__codelineno-12-56\"></a><span class=\"w\">                </span><span class=\"p p-Indicator\">[</span>\n</span><span id=\"__span-12-57\"><a id=\"__codelineno-12-57\" name=\"__codelineno-12-57\" href=\"#__codelineno-12-57\"></a><span class=\"w\">                    </span><span class=\"s\">&quot;/usr/local/bin/pg_ctl&quot;</span><span class=\"p p-Indicator\">,</span>\n</span><span id=\"__span-12-58\"><a id=\"__codelineno-12-58\" name=\"__codelineno-12-58\" href=\"#__codelineno-12-58\"></a><span class=\"w\">                    </span><span class=\"s\">&quot;stop&quot;</span><span class=\"p p-Indicator\">,</span>\n</span><span id=\"__span-12-59\"><a id=\"__codelineno-12-59\" name=\"__codelineno-12-59\" href=\"#__codelineno-12-59\"></a><span class=\"w\">                    </span><span class=\"s\">&quot;-D&quot;</span><span class=\"p p-Indicator\">,</span>\n</span><span id=\"__span-12-60\"><a id=\"__codelineno-12-60\" name=\"__codelineno-12-60\" href=\"#__codelineno-12-60\"></a><span class=\"w\">                    </span><span class=\"s\">&quot;/var/lib/postgresql/data&quot;</span><span class=\"p p-Indicator\">,</span>\n</span><span id=\"__span-12-61\"><a id=\"__codelineno-12-61\" name=\"__codelineno-12-61\" href=\"#__codelineno-12-61\"></a><span class=\"w\">                    </span><span class=\"s\">&quot;-w&quot;</span><span class=\"p p-Indicator\">,</span>\n</span><span id=\"__span-12-62\"><a id=\"__codelineno-12-62\" name=\"__codelineno-12-62\" href=\"#__codelineno-12-62\"></a><span class=\"w\">                    </span><span class=\"s\">&quot;-t&quot;</span><span class=\"p p-Indicator\">,</span>\n</span><span id=\"__span-12-63\"><a id=\"__codelineno-12-63\" name=\"__codelineno-12-63\" href=\"#__codelineno-12-63\"></a><span class=\"w\">                    </span><span class=\"s\">&quot;60&quot;</span><span class=\"p p-Indicator\">,</span>\n</span><span id=\"__span-12-64\"><a id=\"__codelineno-12-64\" name=\"__codelineno-12-64\" href=\"#__codelineno-12-64\"></a><span class=\"w\">                    </span><span class=\"s\">&quot;-m&quot;</span><span class=\"p p-Indicator\">,</span>\n</span><span id=\"__span-12-65\"><a id=\"__codelineno-12-65\" name=\"__codelineno-12-65\" href=\"#__codelineno-12-65\"></a><span class=\"w\">                    </span><span class=\"s\">&quot;fast&quot;</span><span class=\"p p-Indicator\">,</span>\n</span><span id=\"__span-12-66\"><a id=\"__codelineno-12-66\" name=\"__codelineno-12-66\" href=\"#__codelineno-12-66\"></a><span class=\"w\">                </span><span class=\"p p-Indicator\">]</span>\n</span><span id=\"__span-12-67\"><a id=\"__codelineno-12-67\" name=\"__codelineno-12-67\" href=\"#__codelineno-12-67\"></a><span class=\"w\">        </span><span class=\"nt\">resources</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-68\"><a id=\"__codelineno-12-68\" name=\"__codelineno-12-68\" href=\"#__codelineno-12-68\"></a><span class=\"w\">            </span><span class=\"nt\">requests</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-69\"><a id=\"__codelineno-12-69\" name=\"__codelineno-12-69\" href=\"#__codelineno-12-69\"></a><span class=\"w\">            </span><span class=\"nt\">cpu</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">100m</span>\n</span><span id=\"__span-12-70\"><a id=\"__codelineno-12-70\" name=\"__codelineno-12-70\" href=\"#__codelineno-12-70\"></a><span class=\"w\">            </span><span class=\"nt\">memory</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">100Mi</span>\n</span><span id=\"__span-12-71\"><a id=\"__codelineno-12-71\" name=\"__codelineno-12-71\" href=\"#__codelineno-12-71\"></a><span class=\"w\">            </span><span class=\"nt\">limits</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-72\"><a id=\"__codelineno-12-72\" name=\"__codelineno-12-72\" href=\"#__codelineno-12-72\"></a><span class=\"w\">            </span><span class=\"nt\">cpu</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">250m</span>\n</span><span id=\"__span-12-73\"><a id=\"__codelineno-12-73\" name=\"__codelineno-12-73\" href=\"#__codelineno-12-73\"></a><span class=\"w\">            </span><span class=\"nt\">memory</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">600Mi</span>\n</span><span id=\"__span-12-74\"><a id=\"__codelineno-12-74\" name=\"__codelineno-12-74\" href=\"#__codelineno-12-74\"></a><span class=\"w\">        </span><span class=\"nt\">volumeMounts</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-75\"><a id=\"__codelineno-12-75\" name=\"__codelineno-12-75\" href=\"#__codelineno-12-75\"></a><span class=\"w\">            </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">mountPath</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">/var/lib/postgresql/data/</span>\n</span><span id=\"__span-12-76\"><a id=\"__codelineno-12-76\" name=\"__codelineno-12-76\" href=\"#__codelineno-12-76\"></a><span class=\"w\">            </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">database</span>\n</span><span id=\"__span-12-77\"><a id=\"__codelineno-12-77\" name=\"__codelineno-12-77\" href=\"#__codelineno-12-77\"></a><span class=\"w\">    </span><span class=\"nt\">volumes</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-78\"><a id=\"__codelineno-12-78\" name=\"__codelineno-12-78\" href=\"#__codelineno-12-78\"></a><span class=\"w\">        </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">database</span>\n</span><span id=\"__span-12-79\"><a id=\"__codelineno-12-79\" name=\"__codelineno-12-79\" href=\"#__codelineno-12-79\"></a><span class=\"w\">        </span><span class=\"nt\">persistentVolumeClaim</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-80\"><a id=\"__codelineno-12-80\" name=\"__codelineno-12-80\" href=\"#__codelineno-12-80\"></a><span class=\"w\">            </span><span class=\"nt\">claimName</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">database</span>\n</span><span id=\"__span-12-81\"><a id=\"__codelineno-12-81\" name=\"__codelineno-12-81\" href=\"#__codelineno-12-81\"></a><span class=\"nn\">---</span>\n</span><span id=\"__span-12-82\"><a id=\"__codelineno-12-82\" name=\"__codelineno-12-82\" href=\"#__codelineno-12-82\"></a><span class=\"nt\">apiVersion</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">apps/v1</span>\n</span><span id=\"__span-12-83\"><a id=\"__codelineno-12-83\" name=\"__codelineno-12-83\" href=\"#__codelineno-12-83\"></a><span class=\"nt\">kind</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">Deployment</span>\n</span><span id=\"__span-12-84\"><a id=\"__codelineno-12-84\" name=\"__codelineno-12-84\" href=\"#__codelineno-12-84\"></a><span class=\"nt\">metadata</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-85\"><a id=\"__codelineno-12-85\" name=\"__codelineno-12-85\" href=\"#__codelineno-12-85\"></a><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin</span>\n</span><span id=\"__span-12-86\"><a id=\"__codelineno-12-86\" name=\"__codelineno-12-86\" href=\"#__codelineno-12-86\"></a><span class=\"nt\">namespace</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin</span>\n</span><span id=\"__span-12-87\"><a id=\"__codelineno-12-87\" name=\"__codelineno-12-87\" href=\"#__codelineno-12-87\"></a><span class=\"nt\">spec</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-88\"><a id=\"__codelineno-12-88\" name=\"__codelineno-12-88\" href=\"#__codelineno-12-88\"></a><span class=\"nt\">replicas</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">1</span>\n</span><span id=\"__span-12-89\"><a id=\"__codelineno-12-89\" name=\"__codelineno-12-89\" href=\"#__codelineno-12-89\"></a><span class=\"nt\">selector</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-90\"><a id=\"__codelineno-12-90\" name=\"__codelineno-12-90\" href=\"#__codelineno-12-90\"></a><span class=\"w\">    </span><span class=\"nt\">matchLabels</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-91\"><a id=\"__codelineno-12-91\" name=\"__codelineno-12-91\" href=\"#__codelineno-12-91\"></a><span class=\"w\">    </span><span class=\"nt\">app</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin</span>\n</span><span id=\"__span-12-92\"><a id=\"__codelineno-12-92\" name=\"__codelineno-12-92\" href=\"#__codelineno-12-92\"></a><span class=\"nt\">template</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-93\"><a id=\"__codelineno-12-93\" name=\"__codelineno-12-93\" href=\"#__codelineno-12-93\"></a><span class=\"w\">    </span><span class=\"nt\">metadata</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-94\"><a id=\"__codelineno-12-94\" name=\"__codelineno-12-94\" href=\"#__codelineno-12-94\"></a><span class=\"w\">    </span><span class=\"nt\">labels</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-95\"><a id=\"__codelineno-12-95\" name=\"__codelineno-12-95\" href=\"#__codelineno-12-95\"></a><span class=\"w\">        </span><span class=\"nt\">app</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin</span>\n</span><span id=\"__span-12-96\"><a id=\"__codelineno-12-96\" name=\"__codelineno-12-96\" href=\"#__codelineno-12-96\"></a><span class=\"w\">    </span><span class=\"nt\">spec</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-97\"><a id=\"__codelineno-12-97\" name=\"__codelineno-12-97\" href=\"#__codelineno-12-97\"></a><span class=\"w\">    </span><span class=\"nt\">containers</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-98\"><a id=\"__codelineno-12-98\" name=\"__codelineno-12-98\" href=\"#__codelineno-12-98\"></a><span class=\"w\">        </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin</span>\n</span><span id=\"__span-12-99\"><a id=\"__codelineno-12-99\" name=\"__codelineno-12-99\" href=\"#__codelineno-12-99\"></a><span class=\"w\">        </span><span class=\"nt\">image</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">ghcr.io/atuinsh/atuin:v18.4.0</span><span class=\"w\"> </span><span class=\"c1\"># Using a specific version as recommended</span>\n</span><span id=\"__span-12-100\"><a id=\"__codelineno-12-100\" name=\"__codelineno-12-100\" href=\"#__codelineno-12-100\"></a><span class=\"w\">        </span><span class=\"nt\">args</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-101\"><a id=\"__codelineno-12-101\" name=\"__codelineno-12-101\" href=\"#__codelineno-12-101\"></a><span class=\"w\">            </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">server</span>\n</span><span id=\"__span-12-102\"><a id=\"__codelineno-12-102\" name=\"__codelineno-12-102\" href=\"#__codelineno-12-102\"></a><span class=\"w\">            </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">start</span>\n</span><span id=\"__span-12-103\"><a id=\"__codelineno-12-103\" name=\"__codelineno-12-103\" href=\"#__codelineno-12-103\"></a><span class=\"w\">        </span><span class=\"nt\">env</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-104\"><a id=\"__codelineno-12-104\" name=\"__codelineno-12-104\" href=\"#__codelineno-12-104\"></a><span class=\"w\">            </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">ATUIN_DB_URI</span>\n</span><span id=\"__span-12-105\"><a id=\"__codelineno-12-105\" name=\"__codelineno-12-105\" href=\"#__codelineno-12-105\"></a><span class=\"w\">            </span><span class=\"nt\">valueFrom</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-106\"><a id=\"__codelineno-12-106\" name=\"__codelineno-12-106\" href=\"#__codelineno-12-106\"></a><span class=\"w\">                </span><span class=\"nt\">secretKeyRef</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-107\"><a id=\"__codelineno-12-107\" name=\"__codelineno-12-107\" href=\"#__codelineno-12-107\"></a><span class=\"w\">                </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin-secrets</span>\n</span><span id=\"__span-12-108\"><a id=\"__codelineno-12-108\" name=\"__codelineno-12-108\" href=\"#__codelineno-12-108\"></a><span class=\"w\">                </span><span class=\"nt\">key</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">ATUIN_DB_URI</span>\n</span><span id=\"__span-12-109\"><a id=\"__codelineno-12-109\" name=\"__codelineno-12-109\" href=\"#__codelineno-12-109\"></a><span class=\"w\">            </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">ATUIN_HOST</span>\n</span><span id=\"__span-12-110\"><a id=\"__codelineno-12-110\" name=\"__codelineno-12-110\" href=\"#__codelineno-12-110\"></a><span class=\"w\">            </span><span class=\"nt\">valueFrom</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-111\"><a id=\"__codelineno-12-111\" name=\"__codelineno-12-111\" href=\"#__codelineno-12-111\"></a><span class=\"w\">                </span><span class=\"nt\">configMapKeyRef</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-112\"><a id=\"__codelineno-12-112\" name=\"__codelineno-12-112\" href=\"#__codelineno-12-112\"></a><span class=\"w\">                </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin-config</span>\n</span><span id=\"__span-12-113\"><a id=\"__codelineno-12-113\" name=\"__codelineno-12-113\" href=\"#__codelineno-12-113\"></a><span class=\"w\">                </span><span class=\"nt\">key</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">ATUIN_HOST</span>\n</span><span id=\"__span-12-114\"><a id=\"__codelineno-12-114\" name=\"__codelineno-12-114\" href=\"#__codelineno-12-114\"></a><span class=\"w\">            </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">ATUIN_PORT</span>\n</span><span id=\"__span-12-115\"><a id=\"__codelineno-12-115\" name=\"__codelineno-12-115\" href=\"#__codelineno-12-115\"></a><span class=\"w\">            </span><span class=\"nt\">valueFrom</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-116\"><a id=\"__codelineno-12-116\" name=\"__codelineno-12-116\" href=\"#__codelineno-12-116\"></a><span class=\"w\">                </span><span class=\"nt\">configMapKeyRef</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-117\"><a id=\"__codelineno-12-117\" name=\"__codelineno-12-117\" href=\"#__codelineno-12-117\"></a><span class=\"w\">                </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin-config</span>\n</span><span id=\"__span-12-118\"><a id=\"__codelineno-12-118\" name=\"__codelineno-12-118\" href=\"#__codelineno-12-118\"></a><span class=\"w\">                </span><span class=\"nt\">key</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">ATUIN_PORT</span>\n</span><span id=\"__span-12-119\"><a id=\"__codelineno-12-119\" name=\"__codelineno-12-119\" href=\"#__codelineno-12-119\"></a><span class=\"w\">            </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">ATUIN_OPEN_REGISTRATION</span>\n</span><span id=\"__span-12-120\"><a id=\"__codelineno-12-120\" name=\"__codelineno-12-120\" href=\"#__codelineno-12-120\"></a><span class=\"w\">            </span><span class=\"nt\">valueFrom</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-121\"><a id=\"__codelineno-12-121\" name=\"__codelineno-12-121\" href=\"#__codelineno-12-121\"></a><span class=\"w\">                </span><span class=\"nt\">configMapKeyRef</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-122\"><a id=\"__codelineno-12-122\" name=\"__codelineno-12-122\" href=\"#__codelineno-12-122\"></a><span class=\"w\">                </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin-config</span>\n</span><span id=\"__span-12-123\"><a id=\"__codelineno-12-123\" name=\"__codelineno-12-123\" href=\"#__codelineno-12-123\"></a><span class=\"w\">                </span><span class=\"nt\">key</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">ATUIN_OPEN_REGISTRATION</span>\n</span><span id=\"__span-12-124\"><a id=\"__codelineno-12-124\" name=\"__codelineno-12-124\" href=\"#__codelineno-12-124\"></a><span class=\"w\">        </span><span class=\"nt\">ports</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-125\"><a id=\"__codelineno-12-125\" name=\"__codelineno-12-125\" href=\"#__codelineno-12-125\"></a><span class=\"w\">            </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">containerPort</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">8888</span>\n</span><span id=\"__span-12-126\"><a id=\"__codelineno-12-126\" name=\"__codelineno-12-126\" href=\"#__codelineno-12-126\"></a><span class=\"w\">        </span><span class=\"nt\">resources</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-127\"><a id=\"__codelineno-12-127\" name=\"__codelineno-12-127\" href=\"#__codelineno-12-127\"></a><span class=\"w\">            </span><span class=\"nt\">limits</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-128\"><a id=\"__codelineno-12-128\" name=\"__codelineno-12-128\" href=\"#__codelineno-12-128\"></a><span class=\"w\">            </span><span class=\"nt\">cpu</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">250m</span>\n</span><span id=\"__span-12-129\"><a id=\"__codelineno-12-129\" name=\"__codelineno-12-129\" href=\"#__codelineno-12-129\"></a><span class=\"w\">            </span><span class=\"nt\">memory</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">1Gi</span>\n</span><span id=\"__span-12-130\"><a id=\"__codelineno-12-130\" name=\"__codelineno-12-130\" href=\"#__codelineno-12-130\"></a><span class=\"w\">            </span><span class=\"nt\">requests</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-131\"><a id=\"__codelineno-12-131\" name=\"__codelineno-12-131\" href=\"#__codelineno-12-131\"></a><span class=\"w\">            </span><span class=\"nt\">cpu</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">250m</span>\n</span><span id=\"__span-12-132\"><a id=\"__codelineno-12-132\" name=\"__codelineno-12-132\" href=\"#__codelineno-12-132\"></a><span class=\"w\">            </span><span class=\"nt\">memory</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">1Gi</span>\n</span><span id=\"__span-12-133\"><a id=\"__codelineno-12-133\" name=\"__codelineno-12-133\" href=\"#__codelineno-12-133\"></a><span class=\"w\">        </span><span class=\"nt\">volumeMounts</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-134\"><a id=\"__codelineno-12-134\" name=\"__codelineno-12-134\" href=\"#__codelineno-12-134\"></a><span class=\"w\">            </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">mountPath</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">/config</span>\n</span><span id=\"__span-12-135\"><a id=\"__codelineno-12-135\" name=\"__codelineno-12-135\" href=\"#__codelineno-12-135\"></a><span class=\"w\">            </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin-config</span>\n</span><span id=\"__span-12-136\"><a id=\"__codelineno-12-136\" name=\"__codelineno-12-136\" href=\"#__codelineno-12-136\"></a><span class=\"w\">    </span><span class=\"nt\">volumes</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-137\"><a id=\"__codelineno-12-137\" name=\"__codelineno-12-137\" href=\"#__codelineno-12-137\"></a><span class=\"w\">        </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin-config</span>\n</span><span id=\"__span-12-138\"><a id=\"__codelineno-12-138\" name=\"__codelineno-12-138\" href=\"#__codelineno-12-138\"></a><span class=\"w\">        </span><span class=\"nt\">persistentVolumeClaim</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-139\"><a id=\"__codelineno-12-139\" name=\"__codelineno-12-139\" href=\"#__codelineno-12-139\"></a><span class=\"w\">            </span><span class=\"nt\">claimName</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin-config</span>\n</span><span id=\"__span-12-140\"><a id=\"__codelineno-12-140\" name=\"__codelineno-12-140\" href=\"#__codelineno-12-140\"></a>\n</span><span id=\"__span-12-141\"><a id=\"__codelineno-12-141\" name=\"__codelineno-12-141\" href=\"#__codelineno-12-141\"></a><span class=\"nn\">---</span>\n</span><span id=\"__span-12-142\"><a id=\"__codelineno-12-142\" name=\"__codelineno-12-142\" href=\"#__codelineno-12-142\"></a><span class=\"c1\"># ingress.yaml</span>\n</span><span id=\"__span-12-143\"><a id=\"__codelineno-12-143\" name=\"__codelineno-12-143\" href=\"#__codelineno-12-143\"></a>\n</span><span id=\"__span-12-144\"><a id=\"__codelineno-12-144\" name=\"__codelineno-12-144\" href=\"#__codelineno-12-144\"></a><span class=\"nt\">apiVersion</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">networking.k8s.io/v1</span>\n</span><span id=\"__span-12-145\"><a id=\"__codelineno-12-145\" name=\"__codelineno-12-145\" href=\"#__codelineno-12-145\"></a><span class=\"nt\">kind</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">Ingress</span>\n</span><span id=\"__span-12-146\"><a id=\"__codelineno-12-146\" name=\"__codelineno-12-146\" href=\"#__codelineno-12-146\"></a><span class=\"nt\">metadata</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-147\"><a id=\"__codelineno-12-147\" name=\"__codelineno-12-147\" href=\"#__codelineno-12-147\"></a><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin</span>\n</span><span id=\"__span-12-148\"><a id=\"__codelineno-12-148\" name=\"__codelineno-12-148\" href=\"#__codelineno-12-148\"></a><span class=\"nt\">namespace</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin</span>\n</span><span id=\"__span-12-149\"><a id=\"__codelineno-12-149\" name=\"__codelineno-12-149\" href=\"#__codelineno-12-149\"></a><span class=\"nt\">annotations</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-150\"><a id=\"__codelineno-12-150\" name=\"__codelineno-12-150\" href=\"#__codelineno-12-150\"></a><span class=\"w\">    </span><span class=\"nt\">traefik.ingress.kubernetes.io/router.entrypoints</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">web</span>\n</span><span id=\"__span-12-151\"><a id=\"__codelineno-12-151\" name=\"__codelineno-12-151\" href=\"#__codelineno-12-151\"></a><span class=\"w\">    </span><span class=\"nt\">traefik.ingress.kubernetes.io/router.middlewares</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s\">&quot;atuin-strip-prefix@kubernetescrd&quot;</span>\n</span><span id=\"__span-12-152\"><a id=\"__codelineno-12-152\" name=\"__codelineno-12-152\" href=\"#__codelineno-12-152\"></a><span class=\"nt\">spec</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-153\"><a id=\"__codelineno-12-153\" name=\"__codelineno-12-153\" href=\"#__codelineno-12-153\"></a><span class=\"nt\">rules</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-154\"><a id=\"__codelineno-12-154\" name=\"__codelineno-12-154\" href=\"#__codelineno-12-154\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">host</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">tyr.local</span>\n</span><span id=\"__span-12-155\"><a id=\"__codelineno-12-155\" name=\"__codelineno-12-155\" href=\"#__codelineno-12-155\"></a><span class=\"w\">    </span><span class=\"nt\">http</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-156\"><a id=\"__codelineno-12-156\" name=\"__codelineno-12-156\" href=\"#__codelineno-12-156\"></a><span class=\"w\">        </span><span class=\"nt\">paths</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-157\"><a id=\"__codelineno-12-157\" name=\"__codelineno-12-157\" href=\"#__codelineno-12-157\"></a><span class=\"w\">        </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">path</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">/atuin</span>\n</span><span id=\"__span-12-158\"><a id=\"__codelineno-12-158\" name=\"__codelineno-12-158\" href=\"#__codelineno-12-158\"></a><span class=\"w\">            </span><span class=\"l l-Scalar l-Scalar-Plain\">pathType</span><span class=\"p p-Indicator\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">Prefix</span>\n</span><span id=\"__span-12-159\"><a id=\"__codelineno-12-159\" name=\"__codelineno-12-159\" href=\"#__codelineno-12-159\"></a><span class=\"w\">            </span><span class=\"l l-Scalar l-Scalar-Plain\">backend</span><span class=\"p p-Indicator\">:</span>\n</span><span id=\"__span-12-160\"><a id=\"__codelineno-12-160\" name=\"__codelineno-12-160\" href=\"#__codelineno-12-160\"></a><span class=\"w\">            </span><span class=\"nt\">service</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-161\"><a id=\"__codelineno-12-161\" name=\"__codelineno-12-161\" href=\"#__codelineno-12-161\"></a><span class=\"w\">                </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin</span>\n</span><span id=\"__span-12-162\"><a id=\"__codelineno-12-162\" name=\"__codelineno-12-162\" href=\"#__codelineno-12-162\"></a><span class=\"w\">                </span><span class=\"nt\">port</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-163\"><a id=\"__codelineno-12-163\" name=\"__codelineno-12-163\" href=\"#__codelineno-12-163\"></a><span class=\"w\">                </span><span class=\"nt\">number</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">8888</span>\n</span><span id=\"__span-12-164\"><a id=\"__codelineno-12-164\" name=\"__codelineno-12-164\" href=\"#__codelineno-12-164\"></a>\n</span><span id=\"__span-12-165\"><a id=\"__codelineno-12-165\" name=\"__codelineno-12-165\" href=\"#__codelineno-12-165\"></a><span class=\"nn\">---</span>\n</span><span id=\"__span-12-166\"><a id=\"__codelineno-12-166\" name=\"__codelineno-12-166\" href=\"#__codelineno-12-166\"></a><span class=\"c1\"># middleware.yaml</span>\n</span><span id=\"__span-12-167\"><a id=\"__codelineno-12-167\" name=\"__codelineno-12-167\" href=\"#__codelineno-12-167\"></a>\n</span><span id=\"__span-12-168\"><a id=\"__codelineno-12-168\" name=\"__codelineno-12-168\" href=\"#__codelineno-12-168\"></a><span class=\"nt\">apiVersion</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">traefik.io/v1alpha1</span>\n</span><span id=\"__span-12-169\"><a id=\"__codelineno-12-169\" name=\"__codelineno-12-169\" href=\"#__codelineno-12-169\"></a><span class=\"nt\">kind</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">Middleware</span>\n</span><span id=\"__span-12-170\"><a id=\"__codelineno-12-170\" name=\"__codelineno-12-170\" href=\"#__codelineno-12-170\"></a><span class=\"nt\">metadata</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-171\"><a id=\"__codelineno-12-171\" name=\"__codelineno-12-171\" href=\"#__codelineno-12-171\"></a><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">strip-prefix</span>\n</span><span id=\"__span-12-172\"><a id=\"__codelineno-12-172\" name=\"__codelineno-12-172\" href=\"#__codelineno-12-172\"></a><span class=\"nt\">namespace</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin</span>\n</span><span id=\"__span-12-173\"><a id=\"__codelineno-12-173\" name=\"__codelineno-12-173\" href=\"#__codelineno-12-173\"></a><span class=\"nt\">spec</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-174\"><a id=\"__codelineno-12-174\" name=\"__codelineno-12-174\" href=\"#__codelineno-12-174\"></a><span class=\"nt\">stripPrefix</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-175\"><a id=\"__codelineno-12-175\" name=\"__codelineno-12-175\" href=\"#__codelineno-12-175\"></a><span class=\"w\">    </span><span class=\"nt\">prefixes</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-176\"><a id=\"__codelineno-12-176\" name=\"__codelineno-12-176\" href=\"#__codelineno-12-176\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">/atuin</span>\n</span><span id=\"__span-12-177\"><a id=\"__codelineno-12-177\" name=\"__codelineno-12-177\" href=\"#__codelineno-12-177\"></a><span class=\"w\">    </span><span class=\"nt\">forceSlash</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">true</span>\n</span><span id=\"__span-12-178\"><a id=\"__codelineno-12-178\" name=\"__codelineno-12-178\" href=\"#__codelineno-12-178\"></a>\n</span><span id=\"__span-12-179\"><a id=\"__codelineno-12-179\" name=\"__codelineno-12-179\" href=\"#__codelineno-12-179\"></a><span class=\"nn\">---</span>\n</span><span id=\"__span-12-180\"><a id=\"__codelineno-12-180\" name=\"__codelineno-12-180\" href=\"#__codelineno-12-180\"></a><span class=\"c1\"># namespace.yaml</span>\n</span><span id=\"__span-12-181\"><a id=\"__codelineno-12-181\" name=\"__codelineno-12-181\" href=\"#__codelineno-12-181\"></a>\n</span><span id=\"__span-12-182\"><a id=\"__codelineno-12-182\" name=\"__codelineno-12-182\" href=\"#__codelineno-12-182\"></a><span class=\"nt\">apiVersion</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">v1</span>\n</span><span id=\"__span-12-183\"><a id=\"__codelineno-12-183\" name=\"__codelineno-12-183\" href=\"#__codelineno-12-183\"></a><span class=\"nt\">kind</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">Namespace</span>\n</span><span id=\"__span-12-184\"><a id=\"__codelineno-12-184\" name=\"__codelineno-12-184\" href=\"#__codelineno-12-184\"></a><span class=\"nt\">metadata</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-185\"><a id=\"__codelineno-12-185\" name=\"__codelineno-12-185\" href=\"#__codelineno-12-185\"></a><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin</span>\n</span><span id=\"__span-12-186\"><a id=\"__codelineno-12-186\" name=\"__codelineno-12-186\" href=\"#__codelineno-12-186\"></a>\n</span><span id=\"__span-12-187\"><a id=\"__codelineno-12-187\" name=\"__codelineno-12-187\" href=\"#__codelineno-12-187\"></a><span class=\"nn\">---</span>\n</span><span id=\"__span-12-188\"><a id=\"__codelineno-12-188\" name=\"__codelineno-12-188\" href=\"#__codelineno-12-188\"></a><span class=\"c1\"># sealed-secrets.yaml</span>\n</span><span id=\"__span-12-189\"><a id=\"__codelineno-12-189\" name=\"__codelineno-12-189\" href=\"#__codelineno-12-189\"></a>\n</span><span id=\"__span-12-190\"><a id=\"__codelineno-12-190\" name=\"__codelineno-12-190\" href=\"#__codelineno-12-190\"></a><span class=\"nn\">---</span>\n</span><span id=\"__span-12-191\"><a id=\"__codelineno-12-191\" name=\"__codelineno-12-191\" href=\"#__codelineno-12-191\"></a><span class=\"nt\">apiVersion</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">bitnami.com/v1alpha1</span>\n</span><span id=\"__span-12-192\"><a id=\"__codelineno-12-192\" name=\"__codelineno-12-192\" href=\"#__codelineno-12-192\"></a><span class=\"nt\">kind</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">SealedSecret</span>\n</span><span id=\"__span-12-193\"><a id=\"__codelineno-12-193\" name=\"__codelineno-12-193\" href=\"#__codelineno-12-193\"></a><span class=\"nt\">metadata</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-194\"><a id=\"__codelineno-12-194\" name=\"__codelineno-12-194\" href=\"#__codelineno-12-194\"></a><span class=\"nt\">creationTimestamp</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">null</span>\n</span><span id=\"__span-12-195\"><a id=\"__codelineno-12-195\" name=\"__codelineno-12-195\" href=\"#__codelineno-12-195\"></a><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin-secrets</span>\n</span><span id=\"__span-12-196\"><a id=\"__codelineno-12-196\" name=\"__codelineno-12-196\" href=\"#__codelineno-12-196\"></a><span class=\"nt\">namespace</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin</span>\n</span><span id=\"__span-12-197\"><a id=\"__codelineno-12-197\" name=\"__codelineno-12-197\" href=\"#__codelineno-12-197\"></a><span class=\"nt\">spec</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-198\"><a id=\"__codelineno-12-198\" name=\"__codelineno-12-198\" href=\"#__codelineno-12-198\"></a><span class=\"nt\">encryptedData</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-199\"><a id=\"__codelineno-12-199\" name=\"__codelineno-12-199\" href=\"#__codelineno-12-199\"></a><span class=\"w\">    </span><span class=\"nt\">ATUIN_DB_PASSWORD</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">AgBKfphBarMiNX8CIsvjAXqEtRp/Bq+a4y67k/M6bxMm1w/fJUERNqBKaPWqaABfHR4WEk9ePj4CWcVbHb2xVCviX4zYE4pZ9onMvzRGJa2UUl1qRsJGN/ooMRJux+ztfSXJfRzzZxt1QjBlJOmMxG0XjKu0TdahXnI4BMJ2rrBPPmWx9sr4z8YxG8BU/TL8DiJGiD2DtarQWmqSogueGpsOE/9hdeWvW4E7RNlcd7JJ0Hv/nELlhVIUB9fzGoaioDJO6qodYBWNtt2ckyNp3KwoOKXddwRV5tq1ggPKnZOqlHpDgmTaYAFNPXVGIpMNxzUfs+CU0VdT60hx5e3qMbVD86NrnqmbQ38GYc/A7TDrWImSEPjkweLPSTgK5YuQEHJBGYDy9jNNVTMHwfcXkAZkD8swu8+2Whw6No1D2WO2LwewVdTDOynjVhekGk3UF6B2lqIn9TowkIBbZZ6mYYK4VzXRCRXmo2ZiEqDMQK78ejUHdK5m43cZ9M+BEmE3lKzAmgZt+xons/xcisI63pff31urXWZsFylZvnVUnR/l0cp5jmr8KDnMp1WDPf+UyhSlxVvnfAKRyXIGi6jpMQluXVvx/waX4MdqgJMfyn3cQ6tFH4YiZCX6kdNNWjJp5lYxmhRdqWRznCB1vxuWIfXCc9eUT8Kz0Houmw/S8HR11ApNoxopbalC23wdTa9ZXlJdC4bXElfdC8HHwjTcNezDN9mc+4e+WdaKkbuYZljP</span>\n</span><span id=\"__span-12-200\"><a id=\"__codelineno-12-200\" name=\"__codelineno-12-200\" href=\"#__codelineno-12-200\"></a><span class=\"w\">    </span><span class=\"nt\">ATUIN_DB_URI</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">AgCfm2AisGVBlMrOqPvMWOor0e0UXDruZnWVG3klrfSzbtZfrzYF4x+sY7fVLsfUY3RSRF84m13hIJPBxhiO3pFPAs6e6zm5GH7B+8Iem1ijIXWNVW5oc7h/Kas77k1h+TcJTVyZ4gL52oqzZM3cwAX0UdE/enNrvYWoeTsJ0UMbNw3bKZ9Ll0BPfdirdHT8Ve7jMzaDF+d11difPOhyZ7wgK3ykzOGu9G8LbzJ8IwUYYFK/1DETYU76XC/d79tUOwSYxGwf88/r2zjn9ZFA7rnzzEnV7ECR33fSoRJALZMyHMUOp8cxa1rYGPrBRyHhivdhhUnyRgXqAq/oymQo4+cwBHZFSpmtEqafQ8RpuOr2ymRgrxBGfe4n4eLprzY5EUZpFRhgxonb10YL16vg/oAlWObdYkS17ZayQtsfbHBD2udjljQXrjWNIWlT6fXG8JeJth+kFewr9+2c0Rfh9sQJ+F2otBk5x+dbt5xTKppAsAEHIy9lN8/Gbh+U+woCxgP11x+w/HYX9KXDkGHcOiAteYEI7Cf2Eo1TKD7ICVTVfReETWxAzSpKMabltNuM8fuLj6dHakvkQ6PgS537ShhyGofbLQaWTB8AMpwRCIUZme6EkfZuoO2CBt8gCnL3U6geDhHUB4ZGU4g9wPL/FlIqSPaWhafwbjc+PCyXqpOMNHdXtNc7D7bAsWN1Nri3Gk1D4ae0BDTunG/SgX4rlx6zc8kGgmFtJ/cnX//RO40Om2Yf36bdeb3KgDo4Ia49EZDaH7FlRn1cwUax0Gr3Jz4=</span>\n</span><span id=\"__span-12-201\"><a id=\"__codelineno-12-201\" name=\"__codelineno-12-201\" href=\"#__codelineno-12-201\"></a><span class=\"w\">    </span><span class=\"nt\">ATUIN_DB_USERNAME</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">AgAt8yDkKRjmvJtB4ecxOOcuEm1Zcoa8pX1UvtvwAAT4M18PN3JK+6yOyhHuuTwWtWphlQnAjSWx6Bu8usgIxrw9dhBCRxf4pJIaW2VmszUnn1HOtdEFcU6+40PEZ8vJEqCQz/sQoilhZyH06VYecNZFtUHleFAaEFfSGPtxd73lqpjY62fOI8yoGfd/lmXays5vjSx9kUtUVd71FYEOf7P6x+OWlFWsbQ6FepiHygoCXTiCi9umbherpIHWCMZxELja/mNdVZp2wIO+NytedM47LIy2U0FP3b6quPc1H52OK/9AK9TJf/Ke8vUaRDE6TAqv1K0fT5diD4zwERzpNoHKHhnejKj1FOCm6WVcnPHk17zy9Et+kdB+feKpgbeZlolCSJ+JgNWnM2Y3WaovQI4i4yq3ipqQDI1AgY6hHMj1HGNH8gpFjHRy/+UfPd1f4aDO6hGAbL86O2y18VcqD7gESRJ7XVWikJWpU2hIp2FAEpopoqU1QPWyTGvvC46g+gfTARIphn1EzjKymdc4ICb8Viuy/B1oVuwFaD7y9FnNx3tPP4cSuODiG2u6q0j/UTMkAftGqPZUNu3yfkrJHziKUnGc9kuasgAFJKXL2qJuG4VBxNPwTmp2VnJiBysvUb1JTTYd+2uEu4woGmzVfm/9kjkP1rbRk+hAUj5fyW2Nebds9dgD2gXZ2yGOK/S1G0TXnriSQA==</span>\n</span><span id=\"__span-12-202\"><a id=\"__codelineno-12-202\" name=\"__codelineno-12-202\" href=\"#__codelineno-12-202\"></a><span class=\"nt\">template</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-203\"><a id=\"__codelineno-12-203\" name=\"__codelineno-12-203\" href=\"#__codelineno-12-203\"></a><span class=\"w\">    </span><span class=\"nt\">metadata</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-204\"><a id=\"__codelineno-12-204\" name=\"__codelineno-12-204\" href=\"#__codelineno-12-204\"></a><span class=\"w\">    </span><span class=\"nt\">creationTimestamp</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">null</span>\n</span><span id=\"__span-12-205\"><a id=\"__codelineno-12-205\" name=\"__codelineno-12-205\" href=\"#__codelineno-12-205\"></a><span class=\"w\">    </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin-secrets</span>\n</span><span id=\"__span-12-206\"><a id=\"__codelineno-12-206\" name=\"__codelineno-12-206\" href=\"#__codelineno-12-206\"></a><span class=\"w\">    </span><span class=\"nt\">namespace</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin</span>\n</span><span id=\"__span-12-207\"><a id=\"__codelineno-12-207\" name=\"__codelineno-12-207\" href=\"#__codelineno-12-207\"></a><span class=\"w\">    </span><span class=\"nt\">type</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">Opaque</span>\n</span><span id=\"__span-12-208\"><a id=\"__codelineno-12-208\" name=\"__codelineno-12-208\" href=\"#__codelineno-12-208\"></a>\n</span><span id=\"__span-12-209\"><a id=\"__codelineno-12-209\" name=\"__codelineno-12-209\" href=\"#__codelineno-12-209\"></a>\n</span><span id=\"__span-12-210\"><a id=\"__codelineno-12-210\" name=\"__codelineno-12-210\" href=\"#__codelineno-12-210\"></a><span class=\"nn\">---</span>\n</span><span id=\"__span-12-211\"><a id=\"__codelineno-12-211\" name=\"__codelineno-12-211\" href=\"#__codelineno-12-211\"></a><span class=\"c1\"># services.yaml</span>\n</span><span id=\"__span-12-212\"><a id=\"__codelineno-12-212\" name=\"__codelineno-12-212\" href=\"#__codelineno-12-212\"></a>\n</span><span id=\"__span-12-213\"><a id=\"__codelineno-12-213\" name=\"__codelineno-12-213\" href=\"#__codelineno-12-213\"></a><span class=\"nn\">---</span>\n</span><span id=\"__span-12-214\"><a id=\"__codelineno-12-214\" name=\"__codelineno-12-214\" href=\"#__codelineno-12-214\"></a><span class=\"nt\">apiVersion</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">v1</span>\n</span><span id=\"__span-12-215\"><a id=\"__codelineno-12-215\" name=\"__codelineno-12-215\" href=\"#__codelineno-12-215\"></a><span class=\"nt\">kind</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">Service</span>\n</span><span id=\"__span-12-216\"><a id=\"__codelineno-12-216\" name=\"__codelineno-12-216\" href=\"#__codelineno-12-216\"></a><span class=\"nt\">metadata</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-217\"><a id=\"__codelineno-12-217\" name=\"__codelineno-12-217\" href=\"#__codelineno-12-217\"></a><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin</span>\n</span><span id=\"__span-12-218\"><a id=\"__codelineno-12-218\" name=\"__codelineno-12-218\" href=\"#__codelineno-12-218\"></a><span class=\"nt\">namespace</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin</span>\n</span><span id=\"__span-12-219\"><a id=\"__codelineno-12-219\" name=\"__codelineno-12-219\" href=\"#__codelineno-12-219\"></a><span class=\"nt\">spec</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-220\"><a id=\"__codelineno-12-220\" name=\"__codelineno-12-220\" href=\"#__codelineno-12-220\"></a><span class=\"nt\">type</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">ClusterIP</span>\n</span><span id=\"__span-12-221\"><a id=\"__codelineno-12-221\" name=\"__codelineno-12-221\" href=\"#__codelineno-12-221\"></a><span class=\"nt\">ports</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-222\"><a id=\"__codelineno-12-222\" name=\"__codelineno-12-222\" href=\"#__codelineno-12-222\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">port</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">8888</span>\n</span><span id=\"__span-12-223\"><a id=\"__codelineno-12-223\" name=\"__codelineno-12-223\" href=\"#__codelineno-12-223\"></a><span class=\"w\">    </span><span class=\"nt\">targetPort</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">8888</span>\n</span><span id=\"__span-12-224\"><a id=\"__codelineno-12-224\" name=\"__codelineno-12-224\" href=\"#__codelineno-12-224\"></a><span class=\"nt\">selector</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-225\"><a id=\"__codelineno-12-225\" name=\"__codelineno-12-225\" href=\"#__codelineno-12-225\"></a><span class=\"w\">    </span><span class=\"nt\">app</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin</span>\n</span><span id=\"__span-12-226\"><a id=\"__codelineno-12-226\" name=\"__codelineno-12-226\" href=\"#__codelineno-12-226\"></a><span class=\"nn\">---</span>\n</span><span id=\"__span-12-227\"><a id=\"__codelineno-12-227\" name=\"__codelineno-12-227\" href=\"#__codelineno-12-227\"></a><span class=\"nt\">apiVersion</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">v1</span>\n</span><span id=\"__span-12-228\"><a id=\"__codelineno-12-228\" name=\"__codelineno-12-228\" href=\"#__codelineno-12-228\"></a><span class=\"nt\">kind</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">Service</span>\n</span><span id=\"__span-12-229\"><a id=\"__codelineno-12-229\" name=\"__codelineno-12-229\" href=\"#__codelineno-12-229\"></a><span class=\"nt\">metadata</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-230\"><a id=\"__codelineno-12-230\" name=\"__codelineno-12-230\" href=\"#__codelineno-12-230\"></a><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">postgres</span>\n</span><span id=\"__span-12-231\"><a id=\"__codelineno-12-231\" name=\"__codelineno-12-231\" href=\"#__codelineno-12-231\"></a><span class=\"nt\">namespace</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin</span>\n</span><span id=\"__span-12-232\"><a id=\"__codelineno-12-232\" name=\"__codelineno-12-232\" href=\"#__codelineno-12-232\"></a><span class=\"nt\">spec</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-233\"><a id=\"__codelineno-12-233\" name=\"__codelineno-12-233\" href=\"#__codelineno-12-233\"></a><span class=\"nt\">type</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">ClusterIP</span>\n</span><span id=\"__span-12-234\"><a id=\"__codelineno-12-234\" name=\"__codelineno-12-234\" href=\"#__codelineno-12-234\"></a><span class=\"nt\">ports</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-235\"><a id=\"__codelineno-12-235\" name=\"__codelineno-12-235\" href=\"#__codelineno-12-235\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">port</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">5432</span>\n</span><span id=\"__span-12-236\"><a id=\"__codelineno-12-236\" name=\"__codelineno-12-236\" href=\"#__codelineno-12-236\"></a><span class=\"w\">    </span><span class=\"nt\">targetPort</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">5432</span>\n</span><span id=\"__span-12-237\"><a id=\"__codelineno-12-237\" name=\"__codelineno-12-237\" href=\"#__codelineno-12-237\"></a><span class=\"nt\">selector</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-238\"><a id=\"__codelineno-12-238\" name=\"__codelineno-12-238\" href=\"#__codelineno-12-238\"></a><span class=\"w\">    </span><span class=\"nt\">app</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">postgres</span>\n</span><span id=\"__span-12-239\"><a id=\"__codelineno-12-239\" name=\"__codelineno-12-239\" href=\"#__codelineno-12-239\"></a>\n</span><span id=\"__span-12-240\"><a id=\"__codelineno-12-240\" name=\"__codelineno-12-240\" href=\"#__codelineno-12-240\"></a><span class=\"nn\">---</span>\n</span><span id=\"__span-12-241\"><a id=\"__codelineno-12-241\" name=\"__codelineno-12-241\" href=\"#__codelineno-12-241\"></a><span class=\"c1\"># storage.yaml</span>\n</span><span id=\"__span-12-242\"><a id=\"__codelineno-12-242\" name=\"__codelineno-12-242\" href=\"#__codelineno-12-242\"></a>\n</span><span id=\"__span-12-243\"><a id=\"__codelineno-12-243\" name=\"__codelineno-12-243\" href=\"#__codelineno-12-243\"></a><span class=\"nn\">---</span>\n</span><span id=\"__span-12-244\"><a id=\"__codelineno-12-244\" name=\"__codelineno-12-244\" href=\"#__codelineno-12-244\"></a><span class=\"nt\">apiVersion</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">v1</span>\n</span><span id=\"__span-12-245\"><a id=\"__codelineno-12-245\" name=\"__codelineno-12-245\" href=\"#__codelineno-12-245\"></a><span class=\"nt\">kind</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">PersistentVolumeClaim</span>\n</span><span id=\"__span-12-246\"><a id=\"__codelineno-12-246\" name=\"__codelineno-12-246\" href=\"#__codelineno-12-246\"></a><span class=\"nt\">metadata</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-247\"><a id=\"__codelineno-12-247\" name=\"__codelineno-12-247\" href=\"#__codelineno-12-247\"></a><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">database</span>\n</span><span id=\"__span-12-248\"><a id=\"__codelineno-12-248\" name=\"__codelineno-12-248\" href=\"#__codelineno-12-248\"></a><span class=\"nt\">namespace</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin</span>\n</span><span id=\"__span-12-249\"><a id=\"__codelineno-12-249\" name=\"__codelineno-12-249\" href=\"#__codelineno-12-249\"></a><span class=\"nt\">spec</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-250\"><a id=\"__codelineno-12-250\" name=\"__codelineno-12-250\" href=\"#__codelineno-12-250\"></a><span class=\"nt\">accessModes</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-251\"><a id=\"__codelineno-12-251\" name=\"__codelineno-12-251\" href=\"#__codelineno-12-251\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">ReadWriteOnce</span>\n</span><span id=\"__span-12-252\"><a id=\"__codelineno-12-252\" name=\"__codelineno-12-252\" href=\"#__codelineno-12-252\"></a><span class=\"nt\">resources</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-253\"><a id=\"__codelineno-12-253\" name=\"__codelineno-12-253\" href=\"#__codelineno-12-253\"></a><span class=\"w\">    </span><span class=\"nt\">requests</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-254\"><a id=\"__codelineno-12-254\" name=\"__codelineno-12-254\" href=\"#__codelineno-12-254\"></a><span class=\"w\">    </span><span class=\"nt\">storage</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">1Gi</span>\n</span><span id=\"__span-12-255\"><a id=\"__codelineno-12-255\" name=\"__codelineno-12-255\" href=\"#__codelineno-12-255\"></a><span class=\"nn\">---</span>\n</span><span id=\"__span-12-256\"><a id=\"__codelineno-12-256\" name=\"__codelineno-12-256\" href=\"#__codelineno-12-256\"></a><span class=\"nt\">apiVersion</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">v1</span>\n</span><span id=\"__span-12-257\"><a id=\"__codelineno-12-257\" name=\"__codelineno-12-257\" href=\"#__codelineno-12-257\"></a><span class=\"nt\">kind</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">PersistentVolumeClaim</span>\n</span><span id=\"__span-12-258\"><a id=\"__codelineno-12-258\" name=\"__codelineno-12-258\" href=\"#__codelineno-12-258\"></a><span class=\"nt\">metadata</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-259\"><a id=\"__codelineno-12-259\" name=\"__codelineno-12-259\" href=\"#__codelineno-12-259\"></a><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin-config</span>\n</span><span id=\"__span-12-260\"><a id=\"__codelineno-12-260\" name=\"__codelineno-12-260\" href=\"#__codelineno-12-260\"></a><span class=\"nt\">namespace</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">atuin</span>\n</span><span id=\"__span-12-261\"><a id=\"__codelineno-12-261\" name=\"__codelineno-12-261\" href=\"#__codelineno-12-261\"></a><span class=\"nt\">spec</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-262\"><a id=\"__codelineno-12-262\" name=\"__codelineno-12-262\" href=\"#__codelineno-12-262\"></a><span class=\"nt\">accessModes</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-263\"><a id=\"__codelineno-12-263\" name=\"__codelineno-12-263\" href=\"#__codelineno-12-263\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">ReadWriteOnce</span>\n</span><span id=\"__span-12-264\"><a id=\"__codelineno-12-264\" name=\"__codelineno-12-264\" href=\"#__codelineno-12-264\"></a><span class=\"nt\">resources</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-265\"><a id=\"__codelineno-12-265\" name=\"__codelineno-12-265\" href=\"#__codelineno-12-265\"></a><span class=\"w\">    </span><span class=\"nt\">requests</span><span class=\"p\">:</span>\n</span><span id=\"__span-12-266\"><a id=\"__codelineno-12-266\" name=\"__codelineno-12-266\" href=\"#__codelineno-12-266\"></a><span class=\"w\">    </span><span class=\"nt\">storage</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">100Mi</span>\n</span></code></pre></div>\n</details>", "image": null, "date_published": "2025-01-24T00:00:00+00:00", "authors": [], "tags": null}, {"id": "https://kplauritzen.dk/2025-01-18-homelab-1/", "url": "https://kplauritzen.dk/2025-01-18-homelab-1/", "title": "Homelab", "content_html": "<h1 id=\"homelab\">Homelab</h1>\n<h2 id=\"starting-the-homelab\">Starting the homelab</h2>\n<p>I found an old mini PC (ASUS Mini PC PN30), left in a drawer from when I thought I needed it to run a Plex media server.\nWith a sudden (unexpected) burst of motivation I decided to run a local kubernetes cluster on it.\n(In hindsight, I think I might also have been inspired to try self-hosting an RSS reader by <a href=\"https://joeyehand.com/blog/2025/01/15/i-ditched-the-algorithm-for-rssand-you-should-too/\">this</a> post. I just got distracted, by deciding to self-host using kubernetes).</p>\n<h3 id=\"making-a-plan\">Making a plan</h3>\n<p>I asked ChatGPT and Claude for help on how to set up a simple kubernetes setup at home.\nAfter some back-and-forth I landed on installing Debian with no graphical desktop environment, and then installing <code>k3s</code>.\nThe choice of <code>k3s</code> was mainly made to limit the resource requirements. The Mini PC is not exactly beefy, with an underpowered CPU and only 4GB RAM (While trying to confirm this number, I found the listing for this on <a href=\"https://www.amazon.com.au/ASUS-Barebone-Integrated-RadeonTM-Bluetooth-PN30-BBE001MV/dp/B07VL3HJGC\">Amazon</a> and it claims that I can upgrade to 8GB RAM. I might do that at some point).</p>\n<h3 id=\"install-debian\">Install Debian</h3>\n<p>I downloaded an ISO of Debian 12 and made a bootable usb.\nI connected the Mini PC to a monitor, keyboard and mouse and booted the Debian installer from the usb stick.\nI selected graphical installer and followed the guidance.\nI did not create a root user, instead letting my own user get sudo privileges.\nI did not install a desktop environment.\nI gave it the hostname <code>tyr</code>.\nI made sure to select SSH, to allow access after I unplug the peripherals.</p>\n<h3 id=\"install-tools\">Install tools</h3>\n<div class=\"language-sh highlight\"><pre><span></span><code><span id=\"__span-0-1\"><a id=\"__codelineno-0-1\" name=\"__codelineno-0-1\" href=\"#__codelineno-0-1\"></a>sudo<span class=\"w\"> </span>apt-get<span class=\"w\"> </span>update<span class=\"w\"> </span><span class=\"o\">&amp;&amp;</span><span class=\"w\"> </span>sudo<span class=\"w\"> </span>apt-get<span class=\"w\"> </span>upgrade\n</span><span id=\"__span-0-2\"><a id=\"__codelineno-0-2\" name=\"__codelineno-0-2\" href=\"#__codelineno-0-2\"></a>sudo<span class=\"w\"> </span>apt-get<span class=\"w\"> </span>install<span class=\"w\"> </span>-y<span class=\"w\"> </span>vim<span class=\"w\"> </span>git<span class=\"w\"> </span>curl<span class=\"w\"> </span>wget<span class=\"w\"> </span>htop\n</span></code></pre></div>\n<p>I tried accessing the mini PC over shh from my desktop.</p>\n<div class=\"language-sh highlight\"><pre><span></span><code><span id=\"__span-1-1\"><a id=\"__codelineno-1-1\" name=\"__codelineno-1-1\" href=\"#__codelineno-1-1\"></a>ssh<span class=\"w\"> </span>kasper@tyr.local\n</span></code></pre></div>\n<p>This did not work, but using the local IP directly works fine.</p>\n<p>I really want to use the <code>hostname.local</code> thing, I learned that it is called <a href=\"https://en.wikipedia.org/wiki/Multicast_DNS\">mDNS</a>, and I need a mDNS service.\nI installed <a href=\"https://en.wikipedia.org/wiki/Avahi_(software)\">Avahi</a>, both on my desktop and on the Mini PC</p>\n<div class=\"language-sh highlight\"><pre><span></span><code><span id=\"__span-2-1\"><a id=\"__codelineno-2-1\" name=\"__codelineno-2-1\" href=\"#__codelineno-2-1\"></a>sudo<span class=\"w\"> </span>apt-get<span class=\"w\"> </span>install<span class=\"w\"> </span>avahi-daemon\n</span></code></pre></div>\n<h3 id=\"install-k3s\">Install <code>k3s</code></h3>\n<p>Now, to install <code>k3s</code>. Following docs at <a href=\"https://k3s.io/\">https://k3s.io/</a>.</p>\n<div class=\"language-sh highlight\"><pre><span></span><code><span id=\"__span-3-1\"><a id=\"__codelineno-3-1\" name=\"__codelineno-3-1\" href=\"#__codelineno-3-1\"></a>curl<span class=\"w\"> </span>-sfL<span class=\"w\"> </span>https://get.k3s.io<span class=\"w\"> </span><span class=\"p\">|</span><span class=\"w\"> </span>sh<span class=\"w\"> </span>-<span class=\"w\"> </span>\n</span></code></pre></div>\n<p>After a minute, the kubernetes cluster is running and I can query it from <code>tyr</code></p>\n<div class=\"language-console highlight\"><pre><span></span><code><span id=\"__span-4-1\"><a id=\"__codelineno-4-1\" name=\"__codelineno-4-1\" href=\"#__codelineno-4-1\"></a><span class=\"gp\">$ </span>sudo<span class=\"w\"> </span>k3s<span class=\"w\"> </span>kubectl<span class=\"w\"> </span>get<span class=\"w\"> </span>node<span class=\"w\"> </span>\n</span><span id=\"__span-4-2\"><a id=\"__codelineno-4-2\" name=\"__codelineno-4-2\" href=\"#__codelineno-4-2\"></a><span class=\"go\">NAME   STATUS   ROLES                  AGE   VERSION</span>\n</span><span id=\"__span-4-3\"><a id=\"__codelineno-4-3\" name=\"__codelineno-4-3\" href=\"#__codelineno-4-3\"></a><span class=\"go\">tyr    Ready    control-plane,master   15h   v1.31.4+k3s1</span>\n</span></code></pre></div>\n<p>Next, I want to access it from my desktop.\nFollowing the <a href=\"https://docs.k3s.io/cluster-access\">k3s guide</a> I copy <code>/etc/rancher/k3s/k3s.yaml</code> from <code>tyr</code> to <code>~/.kube/config</code> my desktop with <code>scp</code>, and edit the <code>server</code> field to point to IP of <code>tyr</code>.\nI tried a lot get <code>tyr.local</code> to resolve instead of the IP, but as far as I can tell, <code>kubectl</code> is not using the mDNS stuff from above.\nHere is the last chat message (in a long back-and-forth) from <code>o1</code> on why <code>.local</code> does not work.</p>\n<blockquote>\n<p>A statically compiled binary often <strong>does not</strong> use the system's usual NSS (Name Service Switch) mechanisms\u2014like <code>/etc/nsswitch.conf</code> and <code>libnss-mdns</code>\u2014for hostname resolution. Instead, it typically performs \"pure DNS\" lookups.</p>\n<p>That explains why:</p>\n<ul>\n<li><strong>ping tyr.local</strong> succeeds, because it honors <code>nsswitch.conf</code> and uses Avahi/mDNS.</li>\n<li><strong>kubectl</strong> fails on <code>tyr.local</code>, because it bypasses your local mDNS setup and tries querying a DNS server that doesn't know about <code>.local</code> names.'</li>\n</ul>\n</blockquote>\n<p>ChatGPT suggest some ways to fix it, but the simplest seemed to be to just plug in the IP.</p>\n<p>I made sure to go to my router and reserve the local IP address of <code>tyr</code>, so it does not change after a reboot or something.</p>\n<p>And finally, I can run the following from my desktop</p>\n<div class=\"language-console highlight\"><pre><span></span><code><span id=\"__span-5-1\"><a id=\"__codelineno-5-1\" name=\"__codelineno-5-1\" href=\"#__codelineno-5-1\"></a><span class=\"gp\">$ </span>kubectl<span class=\"w\"> </span>get<span class=\"w\"> </span>node<span class=\"w\"> </span>\n</span><span id=\"__span-5-2\"><a id=\"__codelineno-5-2\" name=\"__codelineno-5-2\" href=\"#__codelineno-5-2\"></a><span class=\"go\">NAME   STATUS   ROLES                  AGE   VERSION</span>\n</span><span id=\"__span-5-3\"><a id=\"__codelineno-5-3\" name=\"__codelineno-5-3\" href=\"#__codelineno-5-3\"></a><span class=\"go\">tyr    Ready    control-plane,master   44h   v1.31.4+k3s1</span>\n</span></code></pre></div>", "image": null, "date_published": "2025-01-18T00:00:00+00:00", "authors": [], "tags": null}, {"id": "https://kplauritzen.dk/2025-01-17-migrate-mkdocs/", "url": "https://kplauritzen.dk/2025-01-17-migrate-mkdocs/", "title": "Migrating static site from Jekyll to MkDocs", "content_html": "<h1 id=\"migrating-static-site-from-jekyll-to-mkdocs\">Migrating static site from Jekyll to MkDocs</h1>\n<h2 id=\"intro\">Intro</h2>\n<p>I have posted (very) few post to my <a href=\"http://www.kplauritzen.dk\">blog</a> over the years.\nRecently, one of the things holding me back from posting is that I can't really build it locally any more.\nI'm using <a href=\"https://pages.github.com/\">Github Pages</a>, and although it probably very easy to use, I just use it so rarely that I don't really know what is going on.</p>\n<p>My brief guide says to run <code>bundle install</code>, but I don't have <code>bundle</code> installed. I also don't know what it is.\n<a href=\"https://docs.github.com/en/pages/setting-up-a-github-pages-site-with-jekyll/testing-your-github-pages-site-locally-with-jekyll\">Github</a> tells me to install Jekyll and Ruby. I don't have either of them.</p>\n<p>At work I use Python a lot, and I have created a few docs sites with <a href=\"https://www.mkdocs.org/\">MkDocs</a>, with <a href=\"https://squidfunk.github.io/mkdocs-material/\">Material for MkDocs</a> helping out in making everything pretty. I want to use that tool-stack instead.\nAll the content is markdown anyway, so it should not be too bad.</p>\n<h2 id=\"build-locally\">Build locally</h2>\n<p>I start by cloning <a href=\"https://github.com/KPLauritzen/kplauritzen.github.io\">https://github.com/KPLauritzen/kplauritzen.github.io</a> and opening it VSCode.</p>\n<p>Let's create a justfile to document how to interact with the repo.</p>\n<div class=\"language-makefile highlight\"><span class=\"filename\">justfile</span><pre><span></span><code><span id=\"__span-0-1\"><a id=\"__codelineno-0-1\" name=\"__codelineno-0-1\" href=\"#__codelineno-0-1\"></a><span class=\"nf\">install</span><span class=\"o\">:</span>\n</span><span id=\"__span-0-2\"><a id=\"__codelineno-0-2\" name=\"__codelineno-0-2\" href=\"#__codelineno-0-2\"></a><span class=\"w\">  </span>uv<span class=\"w\"> </span>sync<span class=\"w\"> </span>--all-extras\n</span><span id=\"__span-0-3\"><a id=\"__codelineno-0-3\" name=\"__codelineno-0-3\" href=\"#__codelineno-0-3\"></a><span class=\"w\">  </span>uv<span class=\"w\"> </span>run<span class=\"w\"> </span>pre-commit<span class=\"w\"> </span>install\n</span><span id=\"__span-0-4\"><a id=\"__codelineno-0-4\" name=\"__codelineno-0-4\" href=\"#__codelineno-0-4\"></a>\n</span><span id=\"__span-0-5\"><a id=\"__codelineno-0-5\" name=\"__codelineno-0-5\" href=\"#__codelineno-0-5\"></a><span class=\"nf\">lint</span><span class=\"o\">:</span>\n</span><span id=\"__span-0-6\"><a id=\"__codelineno-0-6\" name=\"__codelineno-0-6\" href=\"#__codelineno-0-6\"></a><span class=\"w\">  </span>uv<span class=\"w\"> </span>run<span class=\"w\"> </span>pre-commit<span class=\"w\"> </span>run<span class=\"w\"> </span>--all-files\n</span></code></pre></div>\n<p>None of this works yet, there is no Python project, but it is a start.</p>\n<p>I set up <code>pyproject.toml</code> with <code>uv init</code> and add some packages with <code>uv add pre-commit mkdocs mkdocs-material</code>.</p>\n<p>Now I just need the most basic config for MkDocs and we are ready to serve some HTML!</p>\n<div class=\"language-yaml highlight\"><span class=\"filename\">mkdocs.yml</span><pre><span></span><code><span id=\"__span-1-1\"><a id=\"__codelineno-1-1\" name=\"__codelineno-1-1\" href=\"#__codelineno-1-1\"></a><span class=\"nt\">site_name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">KPLauritzen.dk</span>\n</span><span id=\"__span-1-2\"><a id=\"__codelineno-1-2\" name=\"__codelineno-1-2\" href=\"#__codelineno-1-2\"></a><span class=\"nt\">docs_dir</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">_posts</span>\n</span></code></pre></div>\n<p>I can see my site locally with <code>mkdocs serve</code></p>\n<p>It's terrible, but it works! I add that as a command in the <code>justfile</code>\n<img alt=\"\" src=\"../images/migrate-1.png\" /></p>\n<h2 id=\"slightly-prettier\">Slightly prettier</h2>\n<p>How little effort can I put in to make this tolerable?</p>\n<ul>\n<li>\n<p>Add a theme to <code>mkdocs.yml</code></p>\n<div class=\"language-yaml highlight\"><pre><span></span><code><span id=\"__span-2-1\"><a id=\"__codelineno-2-1\" name=\"__codelineno-2-1\" href=\"#__codelineno-2-1\"></a><span class=\"w\">  </span><span class=\"nt\">theme</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">material</span>\n</span></code></pre></div>\n</li>\n<li>\n<p>Move <code>index.md</code> to <code>posts_</code> so we don't start with a 404 error.</p>\n</li>\n</ul>\n<p>That's it, now it actually looks serviceable.\n<img alt=\"\" src=\"../images/migrate-2.png\" /></p>\n<p>There is A BUNCH of improvements that could be helpful, but it is too much fun to do. I will save some of that for a rainy day.</p>\n<p>For now, I will just try to create a github workflow to publish this.</p>\n<h2 id=\"publish-again-to-github-pages\">Publish again to Github Pages</h2>\n<p>I already have a Github Action called <code>jekyll.yml</code>. Let's delete that and make a new one.</p>\n<p>I start by stealing the basic outline from <a href=\"https://squidfunk.github.io/mkdocs-material/publishing-your-site/#with-github-actions\">mkdocs-material</a>.\nAfter that, I follow the guide to <a href=\"https://docs.astral.sh/uv/guides/integration/github/\">uv in Github Actions</a>.</p>\n<p>This is the result:</p>\n<div class=\"language-yaml highlight\"><span class=\"filename\">.github/workflows/ci.yml</span><pre><span></span><code><span id=\"__span-3-1\"><a id=\"__codelineno-3-1\" name=\"__codelineno-3-1\" href=\"#__codelineno-3-1\"></a><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">ci</span><span class=\"w\"> </span>\n</span><span id=\"__span-3-2\"><a id=\"__codelineno-3-2\" name=\"__codelineno-3-2\" href=\"#__codelineno-3-2\"></a><span class=\"nt\">on</span><span class=\"p\">:</span>\n</span><span id=\"__span-3-3\"><a id=\"__codelineno-3-3\" name=\"__codelineno-3-3\" href=\"#__codelineno-3-3\"></a><span class=\"w\">  </span><span class=\"nt\">push</span><span class=\"p\">:</span>\n</span><span id=\"__span-3-4\"><a id=\"__codelineno-3-4\" name=\"__codelineno-3-4\" href=\"#__codelineno-3-4\"></a><span class=\"w\">    </span><span class=\"nt\">branches</span><span class=\"p\">:</span>\n</span><span id=\"__span-3-5\"><a id=\"__codelineno-3-5\" name=\"__codelineno-3-5\" href=\"#__codelineno-3-5\"></a><span class=\"w\">      </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">master</span><span class=\"w\"> </span>\n</span><span id=\"__span-3-6\"><a id=\"__codelineno-3-6\" name=\"__codelineno-3-6\" href=\"#__codelineno-3-6\"></a><span class=\"w\">      </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">main</span>\n</span><span id=\"__span-3-7\"><a id=\"__codelineno-3-7\" name=\"__codelineno-3-7\" href=\"#__codelineno-3-7\"></a><span class=\"nt\">permissions</span><span class=\"p\">:</span>\n</span><span id=\"__span-3-8\"><a id=\"__codelineno-3-8\" name=\"__codelineno-3-8\" href=\"#__codelineno-3-8\"></a><span class=\"w\">  </span><span class=\"nt\">contents</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">write</span>\n</span><span id=\"__span-3-9\"><a id=\"__codelineno-3-9\" name=\"__codelineno-3-9\" href=\"#__codelineno-3-9\"></a><span class=\"nt\">jobs</span><span class=\"p\">:</span>\n</span><span id=\"__span-3-10\"><a id=\"__codelineno-3-10\" name=\"__codelineno-3-10\" href=\"#__codelineno-3-10\"></a><span class=\"w\">  </span><span class=\"nt\">deploy</span><span class=\"p\">:</span>\n</span><span id=\"__span-3-11\"><a id=\"__codelineno-3-11\" name=\"__codelineno-3-11\" href=\"#__codelineno-3-11\"></a><span class=\"w\">    </span><span class=\"nt\">runs-on</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">ubuntu-latest</span>\n</span><span id=\"__span-3-12\"><a id=\"__codelineno-3-12\" name=\"__codelineno-3-12\" href=\"#__codelineno-3-12\"></a><span class=\"w\">    </span><span class=\"nt\">steps</span><span class=\"p\">:</span>\n</span><span id=\"__span-3-13\"><a id=\"__codelineno-3-13\" name=\"__codelineno-3-13\" href=\"#__codelineno-3-13\"></a><span class=\"w\">      </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">uses</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">actions/checkout@v4</span>\n</span><span id=\"__span-3-14\"><a id=\"__codelineno-3-14\" name=\"__codelineno-3-14\" href=\"#__codelineno-3-14\"></a><span class=\"w\">      </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">Configure Git Credentials</span>\n</span><span id=\"__span-3-15\"><a id=\"__codelineno-3-15\" name=\"__codelineno-3-15\" href=\"#__codelineno-3-15\"></a><span class=\"w\">        </span><span class=\"nt\">run</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"p p-Indicator\">|</span>\n</span><span id=\"__span-3-16\"><a id=\"__codelineno-3-16\" name=\"__codelineno-3-16\" href=\"#__codelineno-3-16\"></a><span class=\"w\">          </span><span class=\"no\">git config user.name github-actions[bot]</span>\n</span><span id=\"__span-3-17\"><a id=\"__codelineno-3-17\" name=\"__codelineno-3-17\" href=\"#__codelineno-3-17\"></a><span class=\"w\">          </span><span class=\"no\">git config user.email 41898282+github-actions[bot]@users.noreply.github.com</span>\n</span><span id=\"__span-3-18\"><a id=\"__codelineno-3-18\" name=\"__codelineno-3-18\" href=\"#__codelineno-3-18\"></a><span class=\"w\">      </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">Install uv</span>\n</span><span id=\"__span-3-19\"><a id=\"__codelineno-3-19\" name=\"__codelineno-3-19\" href=\"#__codelineno-3-19\"></a><span class=\"w\">        </span><span class=\"nt\">uses</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">astral-sh/setup-uv@v5</span>\n</span><span id=\"__span-3-20\"><a id=\"__codelineno-3-20\" name=\"__codelineno-3-20\" href=\"#__codelineno-3-20\"></a><span class=\"w\">      </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">run</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">echo &quot;cache_id=$(date --utc &#39;+%V&#39;)&quot; &gt;&gt; $GITHUB_ENV</span><span class=\"w\"> </span>\n</span><span id=\"__span-3-21\"><a id=\"__codelineno-3-21\" name=\"__codelineno-3-21\" href=\"#__codelineno-3-21\"></a><span class=\"w\">      </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">uses</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">actions/cache@v4</span>\n</span><span id=\"__span-3-22\"><a id=\"__codelineno-3-22\" name=\"__codelineno-3-22\" href=\"#__codelineno-3-22\"></a><span class=\"w\">        </span><span class=\"nt\">with</span><span class=\"p\">:</span>\n</span><span id=\"__span-3-23\"><a id=\"__codelineno-3-23\" name=\"__codelineno-3-23\" href=\"#__codelineno-3-23\"></a><span class=\"w\">          </span><span class=\"nt\">key</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">mkdocs-material-${{ env.cache_id }}</span>\n</span><span id=\"__span-3-24\"><a id=\"__codelineno-3-24\" name=\"__codelineno-3-24\" href=\"#__codelineno-3-24\"></a><span class=\"w\">          </span><span class=\"nt\">path</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">.cache</span>\n</span><span id=\"__span-3-25\"><a id=\"__codelineno-3-25\" name=\"__codelineno-3-25\" href=\"#__codelineno-3-25\"></a><span class=\"w\">          </span><span class=\"nt\">restore-keys</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"p p-Indicator\">|</span>\n</span><span id=\"__span-3-26\"><a id=\"__codelineno-3-26\" name=\"__codelineno-3-26\" href=\"#__codelineno-3-26\"></a><span class=\"w\">            </span><span class=\"no\">mkdocs-material-</span>\n</span><span id=\"__span-3-27\"><a id=\"__codelineno-3-27\" name=\"__codelineno-3-27\" href=\"#__codelineno-3-27\"></a><span class=\"w\">      </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">run</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">uv run mkdocs gh-deploy --force</span>\n</span></code></pre></div>\n<p>EDIT: After publishing I had some problems with my custom domain, <code>kplauritzen.dk</code>. Every time I ran <code>mkdocs gh-deploy</code> it wanted to deploy to <code>kplauritzen.github.io</code> instead.</p>\n<p>I think the solution is to create a <code>CNAME</code> file in <code>_posts/</code> as that will get picked up during the build.\nSee the <a href=\"https://www.mkdocs.org/user-guide/deploying-your-docs/#custom-domains\">docs</a>.</p>", "image": null, "date_published": "2025-01-17T00:00:00+00:00", "authors": [], "tags": null}, {"id": "https://kplauritzen.dk/2024-11-15-databricks-cli-auth/", "url": "https://kplauritzen.dk/2024-11-15-databricks-cli-auth/", "title": "Use databricks profiles to emulate service principals", "content_html": "<h1 id=\"use-databricks-profiles-to-emulate-service-principals\">Use databricks profiles to emulate service principals</h1>\n<p><code>TL;DR</code>: Edit your <code>~/.databrickscfg</code> file to create a profile for your service principals.</p>\n<h2 id=\"problem-the-feedback-loop-when-developing-a-cicd-pipeline-is-too-slow\">Problem: The feedback loop when developing a CI/CD pipeline is too slow</h2>\n<p>I have a CI/CD pipeline that interacts with a Databricks workspace through the Databricks CLI.\nI usually develop the pipeline locally, testing it against a sandbox Databricks workspace, authenticated as myself.</p>\n<p>But when I deploy the pipeline to the CI/CD environment, it runs as a service principal, first against a dev workspace, then against a prod workspace.</p>\n<p>There can be some issues that only appear when running as a service principal, like permissions errors or workspace configurations. And the feedback loop is too slow: I have to commit, push, wait for the pipeline to run, check the logs, and repeat.</p>\n<p>I want to test the pipeline locally, authenticated as a service principal, to catch these issues earlier.</p>\n<h2 id=\"solution-use-databricks-profiles-to-emulate-service-principals\">Solution: Use databricks profiles to emulate service principals</h2>\n<p>Reading about the one million ways to authenticate to an Azure Databricks workspace is enough to give me a headache (Seriously, <a href=\"https://learn.microsoft.com/en-us/azure/databricks/dev-tools/auth/\">there are too many options</a>).\nI have previously used environment variables to authenticate as a service principal, the various secrets in an <code>.env</code> file, and commenting and un-commenting as needed.\nIt is a mess, and I'm guaranteed to forget to switch back to my user account at some point.</p>\n<p>Instead, I can use databricks profiles to store the different authentication configurations.\nIn <code>~/.databrickscfg</code>, I can create a profile for each service principal, and switch between them with the <code>--profile</code> flag.</p>\n<p>Here is an example of a <code>~/.databrickscfg</code> file with two Service principal profiles:</p>\n<div class=\"language-ini highlight\"><span class=\"filename\">.databrickscfg</span><pre><span></span><code><span id=\"__span-0-1\"><a id=\"__codelineno-0-1\" name=\"__codelineno-0-1\" href=\"#__codelineno-0-1\"></a><span class=\"k\">[DEFAULT]</span>\n</span><span id=\"__span-0-2\"><a id=\"__codelineno-0-2\" name=\"__codelineno-0-2\" href=\"#__codelineno-0-2\"></a><span class=\"na\">host</span><span class=\"w\">  </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"s\">&lt;SOME_HOST&gt;</span>\n</span><span id=\"__span-0-3\"><a id=\"__codelineno-0-3\" name=\"__codelineno-0-3\" href=\"#__codelineno-0-3\"></a><span class=\"na\">token</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"s\">&lt;SOME_TOKEN&gt;</span>\n</span><span id=\"__span-0-4\"><a id=\"__codelineno-0-4\" name=\"__codelineno-0-4\" href=\"#__codelineno-0-4\"></a>\n</span><span id=\"__span-0-5\"><a id=\"__codelineno-0-5\" name=\"__codelineno-0-5\" href=\"#__codelineno-0-5\"></a><span class=\"k\">[project-prod-sp]</span>\n</span><span id=\"__span-0-6\"><a id=\"__codelineno-0-6\" name=\"__codelineno-0-6\" href=\"#__codelineno-0-6\"></a><span class=\"na\">host</span><span class=\"w\">                </span><span class=\"o\">=</span><span class=\"w\"> </span>\n</span><span id=\"__span-0-7\"><a id=\"__codelineno-0-7\" name=\"__codelineno-0-7\" href=\"#__codelineno-0-7\"></a><span class=\"na\">azure_client_id</span><span class=\"w\">     </span><span class=\"o\">=</span><span class=\"w\"> </span>\n</span><span id=\"__span-0-8\"><a id=\"__codelineno-0-8\" name=\"__codelineno-0-8\" href=\"#__codelineno-0-8\"></a><span class=\"na\">azure_client_secret</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span>\n</span><span id=\"__span-0-9\"><a id=\"__codelineno-0-9\" name=\"__codelineno-0-9\" href=\"#__codelineno-0-9\"></a><span class=\"na\">azure_tenant_id</span><span class=\"w\">     </span><span class=\"o\">=</span><span class=\"w\"> </span>\n</span><span id=\"__span-0-10\"><a id=\"__codelineno-0-10\" name=\"__codelineno-0-10\" href=\"#__codelineno-0-10\"></a>\n</span><span id=\"__span-0-11\"><a id=\"__codelineno-0-11\" name=\"__codelineno-0-11\" href=\"#__codelineno-0-11\"></a><span class=\"k\">[project-dev-sp]</span>\n</span><span id=\"__span-0-12\"><a id=\"__codelineno-0-12\" name=\"__codelineno-0-12\" href=\"#__codelineno-0-12\"></a><span class=\"na\">&lt;same setup as above&gt;</span>\n</span></code></pre></div>\n<p>Of course, you should replace the placeholders with the actual values.</p>\n<p>To test what workspace and user your profile is using, you can try the following command:</p>\n<div class=\"language-bash highlight\"><pre><span></span><code><span id=\"__span-1-1\"><a id=\"__codelineno-1-1\" name=\"__codelineno-1-1\" href=\"#__codelineno-1-1\"></a>databricks<span class=\"w\"> </span>auth<span class=\"w\"> </span>describe<span class=\"w\"> </span>--profile<span class=\"w\"> </span>project-prod-sp\n</span></code></pre></div>\n<p>This will also show you where the authentication is coming from (because, as I mentioned above, there are too many ways to authenticate).</p>\n<p>Finally, you can run your pipeline locally, using the <code>--profile</code> flag to specify that you want to use the service principal profile:</p>\n<div class=\"language-bash highlight\"><pre><span></span><code><span id=\"__span-2-1\"><a id=\"__codelineno-2-1\" name=\"__codelineno-2-1\" href=\"#__codelineno-2-1\"></a>databricks<span class=\"w\"> </span>bundle<span class=\"w\"> </span>deploy<span class=\"w\"> </span>--profile<span class=\"w\"> </span>project-dev-sp\n</span></code></pre></div>\n<h2 id=\"alternative-to-using-profile-flag\">Alternative to using <code>--profile</code> flag</h2>\n<p>If you still want to use environment variables, you can set the <code>DATABRICKS_CONFIG_PROFILE</code> variable to the profile name you want to use, e.g.:</p>\n<div class=\"language-ini highlight\"><pre><span></span><code><span id=\"__span-3-1\"><a id=\"__codelineno-3-1\" name=\"__codelineno-3-1\" href=\"#__codelineno-3-1\"></a><span class=\"na\">DATABRICKS_CONFIG_PROFILE</span><span class=\"o\">=</span><span class=\"s\">DEFAULT</span>\n</span></code></pre></div>", "image": null, "date_published": "2024-11-15T00:00:00+00:00", "authors": [], "tags": null}, {"id": "https://kplauritzen.dk/2024-11-13-docker-kafka-test/", "url": "https://kplauritzen.dk/2024-11-13-docker-kafka-test/", "title": "Test kafka clients with Docker", "content_html": "<h1 id=\"test-kafka-clients-with-docker\">Test kafka clients with Docker</h1>\n<p><code>TL;DR</code>: Use <code>pytest-docker</code> to create a test fixture that starts a Kafka container.</p>\n<h2 id=\"problem-i-want-to-test-my-kafka-client-but-i-dont-have-a-kafka-cluster\">Problem: I want to test my Kafka client, but I don't have a Kafka cluster</h2>\n<p>At work, we need to consume and produce messages to some queue. And one of the tools available already is Kafka.</p>\n<p>Before integrating with the existing Kafka cluster, I want to test my client code. I want to ensure that it can consume and produce messages correctly.</p>\n<p>I have an existing <code>BaseQueueService</code> class like this:</p>\n<div class=\"language-python highlight\"><pre><span></span><code><span id=\"__span-0-1\"><a id=\"__codelineno-0-1\" name=\"__codelineno-0-1\" href=\"#__codelineno-0-1\"></a><span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">BaseQueueService</span><span class=\"p\">(</span><span class=\"n\">ABC</span><span class=\"p\">):</span>\n</span><span id=\"__span-0-2\"><a id=\"__codelineno-0-2\" name=\"__codelineno-0-2\" href=\"#__codelineno-0-2\"></a>    <span class=\"nd\">@abstractmethod</span>\n</span><span id=\"__span-0-3\"><a id=\"__codelineno-0-3\" name=\"__codelineno-0-3\" href=\"#__codelineno-0-3\"></a>    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">publish</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">message</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"kc\">None</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-4\"><a id=\"__codelineno-0-4\" name=\"__codelineno-0-4\" href=\"#__codelineno-0-4\"></a>        <span class=\"k\">pass</span>\n</span><span id=\"__span-0-5\"><a id=\"__codelineno-0-5\" name=\"__codelineno-0-5\" href=\"#__codelineno-0-5\"></a>\n</span><span id=\"__span-0-6\"><a id=\"__codelineno-0-6\" name=\"__codelineno-0-6\" href=\"#__codelineno-0-6\"></a>    <span class=\"nd\">@abstractmethod</span>\n</span><span id=\"__span-0-7\"><a id=\"__codelineno-0-7\" name=\"__codelineno-0-7\" href=\"#__codelineno-0-7\"></a>    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">consume</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">str</span> <span class=\"o\">|</span> <span class=\"kc\">None</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-8\"><a id=\"__codelineno-0-8\" name=\"__codelineno-0-8\" href=\"#__codelineno-0-8\"></a>        <span class=\"k\">pass</span>\n</span></code></pre></div>\n<p>with existing implementations for Azure Service Bus and an InMemoryQueue for testing business logic.</p>\n<p>So I want to create a <code>KafkaQueueService</code> class that implements this interface. And I want to test it, but I don't have a Kafka cluster available.</p>\n<h2 id=\"solution-use-docker-to-start-a-kafka-container-for-testing\">Solution: Use docker to start a Kafka container for testing</h2>\n<p>I can use <code>pytest-docker</code> to create a test fixture that starts a Kafka container. This way, I can test my <code>KafkaQueueService</code> class without needing a Kafka cluster.</p>\n<p>This is how I did it:</p>\n<p>A <code>docker-compose.yml</code> file to start a Kafka container:</p>\n<div class=\"language-yaml highlight\"><span class=\"filename\">docker-compose.yml</span><pre><span></span><code><span id=\"__span-1-1\"><a id=\"__codelineno-1-1\" name=\"__codelineno-1-1\" href=\"#__codelineno-1-1\"></a><span class=\"nt\">services</span><span class=\"p\">:</span>\n</span><span id=\"__span-1-2\"><a id=\"__codelineno-1-2\" name=\"__codelineno-1-2\" href=\"#__codelineno-1-2\"></a><span class=\"w\">  </span><span class=\"nt\">zookeeper</span><span class=\"p\">:</span>\n</span><span id=\"__span-1-3\"><a id=\"__codelineno-1-3\" name=\"__codelineno-1-3\" href=\"#__codelineno-1-3\"></a><span class=\"w\">    </span><span class=\"nt\">image</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s\">&#39;confluentinc/cp-zookeeper:latest&#39;</span>\n</span><span id=\"__span-1-4\"><a id=\"__codelineno-1-4\" name=\"__codelineno-1-4\" href=\"#__codelineno-1-4\"></a><span class=\"w\">    </span><span class=\"nt\">environment</span><span class=\"p\">:</span>\n</span><span id=\"__span-1-5\"><a id=\"__codelineno-1-5\" name=\"__codelineno-1-5\" href=\"#__codelineno-1-5\"></a><span class=\"w\">      </span><span class=\"nt\">ZOOKEEPER_CLIENT_PORT</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">2181</span>\n</span><span id=\"__span-1-6\"><a id=\"__codelineno-1-6\" name=\"__codelineno-1-6\" href=\"#__codelineno-1-6\"></a><span class=\"w\">      </span><span class=\"nt\">ZOOKEEPER_TICK_TIME</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">2000</span>\n</span><span id=\"__span-1-7\"><a id=\"__codelineno-1-7\" name=\"__codelineno-1-7\" href=\"#__codelineno-1-7\"></a><span class=\"w\">    </span><span class=\"nt\">ports</span><span class=\"p\">:</span>\n</span><span id=\"__span-1-8\"><a id=\"__codelineno-1-8\" name=\"__codelineno-1-8\" href=\"#__codelineno-1-8\"></a><span class=\"w\">      </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"s\">&quot;2181:2181&quot;</span>\n</span><span id=\"__span-1-9\"><a id=\"__codelineno-1-9\" name=\"__codelineno-1-9\" href=\"#__codelineno-1-9\"></a>\n</span><span id=\"__span-1-10\"><a id=\"__codelineno-1-10\" name=\"__codelineno-1-10\" href=\"#__codelineno-1-10\"></a><span class=\"w\">  </span><span class=\"nt\">kafka</span><span class=\"p\">:</span>\n</span><span id=\"__span-1-11\"><a id=\"__codelineno-1-11\" name=\"__codelineno-1-11\" href=\"#__codelineno-1-11\"></a><span class=\"w\">    </span><span class=\"nt\">image</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s\">&#39;confluentinc/cp-kafka:latest&#39;</span>\n</span><span id=\"__span-1-12\"><a id=\"__codelineno-1-12\" name=\"__codelineno-1-12\" href=\"#__codelineno-1-12\"></a><span class=\"w\">    </span><span class=\"nt\">depends_on</span><span class=\"p\">:</span>\n</span><span id=\"__span-1-13\"><a id=\"__codelineno-1-13\" name=\"__codelineno-1-13\" href=\"#__codelineno-1-13\"></a><span class=\"w\">      </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">zookeeper</span>\n</span><span id=\"__span-1-14\"><a id=\"__codelineno-1-14\" name=\"__codelineno-1-14\" href=\"#__codelineno-1-14\"></a><span class=\"w\">    </span><span class=\"nt\">environment</span><span class=\"p\">:</span>\n</span><span id=\"__span-1-15\"><a id=\"__codelineno-1-15\" name=\"__codelineno-1-15\" href=\"#__codelineno-1-15\"></a><span class=\"w\">      </span><span class=\"nt\">KAFKA_BROKER_ID</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">1</span>\n</span><span id=\"__span-1-16\"><a id=\"__codelineno-1-16\" name=\"__codelineno-1-16\" href=\"#__codelineno-1-16\"></a><span class=\"w\">      </span><span class=\"nt\">KAFKA_ZOOKEEPER_CONNECT</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">zookeeper:2181</span>\n</span><span id=\"__span-1-17\"><a id=\"__codelineno-1-17\" name=\"__codelineno-1-17\" href=\"#__codelineno-1-17\"></a><span class=\"w\">      </span><span class=\"nt\">KAFKA_ADVERTISED_LISTENERS</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092</span>\n</span><span id=\"__span-1-18\"><a id=\"__codelineno-1-18\" name=\"__codelineno-1-18\" href=\"#__codelineno-1-18\"></a><span class=\"w\">      </span><span class=\"nt\">KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">1</span>\n</span><span id=\"__span-1-19\"><a id=\"__codelineno-1-19\" name=\"__codelineno-1-19\" href=\"#__codelineno-1-19\"></a><span class=\"w\">      </span><span class=\"nt\">KAFKA_LISTENER_SECURITY_PROTOCOL_MAP</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT</span>\n</span><span id=\"__span-1-20\"><a id=\"__codelineno-1-20\" name=\"__codelineno-1-20\" href=\"#__codelineno-1-20\"></a><span class=\"w\">    </span><span class=\"nt\">ports</span><span class=\"p\">:</span>\n</span><span id=\"__span-1-21\"><a id=\"__codelineno-1-21\" name=\"__codelineno-1-21\" href=\"#__codelineno-1-21\"></a><span class=\"w\">      </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"s\">&quot;9092:9092&quot;</span>\n</span><span id=\"__span-1-22\"><a id=\"__codelineno-1-22\" name=\"__codelineno-1-22\" href=\"#__codelineno-1-22\"></a><span class=\"w\">    </span><span class=\"nt\">expose</span><span class=\"p\">:</span>\n</span><span id=\"__span-1-23\"><a id=\"__codelineno-1-23\" name=\"__codelineno-1-23\" href=\"#__codelineno-1-23\"></a><span class=\"w\">      </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"s\">&quot;29092&quot;</span>\n</span><span id=\"__span-1-24\"><a id=\"__codelineno-1-24\" name=\"__codelineno-1-24\" href=\"#__codelineno-1-24\"></a>\n</span><span id=\"__span-1-25\"><a id=\"__codelineno-1-25\" name=\"__codelineno-1-25\" href=\"#__codelineno-1-25\"></a><span class=\"w\">  </span><span class=\"nt\">init-kafka</span><span class=\"p\">:</span>\n</span><span id=\"__span-1-26\"><a id=\"__codelineno-1-26\" name=\"__codelineno-1-26\" href=\"#__codelineno-1-26\"></a><span class=\"w\">    </span><span class=\"nt\">image</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s\">&#39;confluentinc/cp-kafka:latest&#39;</span>\n</span><span id=\"__span-1-27\"><a id=\"__codelineno-1-27\" name=\"__codelineno-1-27\" href=\"#__codelineno-1-27\"></a><span class=\"w\">    </span><span class=\"nt\">depends_on</span><span class=\"p\">:</span>\n</span><span id=\"__span-1-28\"><a id=\"__codelineno-1-28\" name=\"__codelineno-1-28\" href=\"#__codelineno-1-28\"></a><span class=\"w\">      </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">kafka</span>\n</span><span id=\"__span-1-29\"><a id=\"__codelineno-1-29\" name=\"__codelineno-1-29\" href=\"#__codelineno-1-29\"></a><span class=\"w\">    </span><span class=\"nt\">entrypoint</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"p p-Indicator\">[</span><span class=\"w\"> </span><span class=\"s\">&#39;/bin/sh&#39;</span><span class=\"p p-Indicator\">,</span><span class=\"w\"> </span><span class=\"s\">&#39;-c&#39;</span><span class=\"w\"> </span><span class=\"p p-Indicator\">]</span>\n</span><span id=\"__span-1-30\"><a id=\"__codelineno-1-30\" name=\"__codelineno-1-30\" href=\"#__codelineno-1-30\"></a><span class=\"w\">    </span><span class=\"nt\">command</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"p p-Indicator\">|</span>\n</span><span id=\"__span-1-31\"><a id=\"__codelineno-1-31\" name=\"__codelineno-1-31\" href=\"#__codelineno-1-31\"></a><span class=\"w\">      </span><span class=\"no\">&quot;</span>\n</span><span id=\"__span-1-32\"><a id=\"__codelineno-1-32\" name=\"__codelineno-1-32\" href=\"#__codelineno-1-32\"></a><span class=\"w\">      </span><span class=\"no\"># blocks until kafka is reachable</span>\n</span><span id=\"__span-1-33\"><a id=\"__codelineno-1-33\" name=\"__codelineno-1-33\" href=\"#__codelineno-1-33\"></a><span class=\"w\">      </span><span class=\"no\">kafka-topics --bootstrap-server kafka:29092 --list</span>\n</span><span id=\"__span-1-34\"><a id=\"__codelineno-1-34\" name=\"__codelineno-1-34\" href=\"#__codelineno-1-34\"></a>\n</span><span id=\"__span-1-35\"><a id=\"__codelineno-1-35\" name=\"__codelineno-1-35\" href=\"#__codelineno-1-35\"></a><span class=\"w\">      </span><span class=\"no\">echo -e &#39;Creating kafka topics&#39;</span>\n</span><span id=\"__span-1-36\"><a id=\"__codelineno-1-36\" name=\"__codelineno-1-36\" href=\"#__codelineno-1-36\"></a><span class=\"w\">      </span><span class=\"no\">kafka-topics --bootstrap-server kafka:29092 --create --if-not-exists --topic testtopic --replication-factor 1 --partitions 1</span>\n</span><span id=\"__span-1-37\"><a id=\"__codelineno-1-37\" name=\"__codelineno-1-37\" href=\"#__codelineno-1-37\"></a><span class=\"w\">      </span><span class=\"no\">kafka-topics --bootstrap-server kafka:29092 --create --if-not-exists --topic input_test_topic --replication-factor 1 --partitions 1</span>\n</span><span id=\"__span-1-38\"><a id=\"__codelineno-1-38\" name=\"__codelineno-1-38\" href=\"#__codelineno-1-38\"></a><span class=\"w\">      </span><span class=\"no\">kafka-topics --bootstrap-server kafka:29092 --create --if-not-exists --topic output_test_topic --replication-factor 1 --partitions 1</span>\n</span><span id=\"__span-1-39\"><a id=\"__codelineno-1-39\" name=\"__codelineno-1-39\" href=\"#__codelineno-1-39\"></a>\n</span><span id=\"__span-1-40\"><a id=\"__codelineno-1-40\" name=\"__codelineno-1-40\" href=\"#__codelineno-1-40\"></a><span class=\"w\">      </span><span class=\"no\">echo -e &#39;Successfully created the following topics:&#39;</span>\n</span><span id=\"__span-1-41\"><a id=\"__codelineno-1-41\" name=\"__codelineno-1-41\" href=\"#__codelineno-1-41\"></a><span class=\"w\">      </span><span class=\"no\">kafka-topics --bootstrap-server kafka:29092 --list</span>\n</span><span id=\"__span-1-42\"><a id=\"__codelineno-1-42\" name=\"__codelineno-1-42\" href=\"#__codelineno-1-42\"></a><span class=\"w\">      </span><span class=\"no\">&quot;</span>\n</span></code></pre></div>\n<p>A <code>conftest.py</code> file to create a test fixture that starts the Kafka container:</p>\n<div class=\"language-python highlight\"><pre><span></span><code><span id=\"__span-2-1\"><a id=\"__codelineno-2-1\" name=\"__codelineno-2-1\" href=\"#__codelineno-2-1\"></a><span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">check_kafka_ready</span><span class=\"p\">(</span><span class=\"n\">required_topics</span><span class=\"p\">,</span> <span class=\"n\">host</span><span class=\"o\">=</span><span class=\"s2\">&quot;localhost&quot;</span><span class=\"p\">,</span> <span class=\"n\">port</span><span class=\"o\">=</span><span class=\"mi\">9092</span><span class=\"p\">):</span>\n</span><span id=\"__span-2-2\"><a id=\"__codelineno-2-2\" name=\"__codelineno-2-2\" href=\"#__codelineno-2-2\"></a>    <span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">confluent_kafka</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">KafkaException</span>\n</span><span id=\"__span-2-3\"><a id=\"__codelineno-2-3\" name=\"__codelineno-2-3\" href=\"#__codelineno-2-3\"></a>    <span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">confluent_kafka.admin</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">AdminClient</span>\n</span><span id=\"__span-2-4\"><a id=\"__codelineno-2-4\" name=\"__codelineno-2-4\" href=\"#__codelineno-2-4\"></a>\n</span><span id=\"__span-2-5\"><a id=\"__codelineno-2-5\" name=\"__codelineno-2-5\" href=\"#__codelineno-2-5\"></a>    <span class=\"k\">try</span><span class=\"p\">:</span>\n</span><span id=\"__span-2-6\"><a id=\"__codelineno-2-6\" name=\"__codelineno-2-6\" href=\"#__codelineno-2-6\"></a>        <span class=\"n\">admin</span> <span class=\"o\">=</span> <span class=\"n\">AdminClient</span><span class=\"p\">({</span><span class=\"s2\">&quot;bootstrap.servers&quot;</span><span class=\"p\">:</span> <span class=\"sa\">f</span><span class=\"s2\">&quot;</span><span class=\"si\">{</span><span class=\"n\">host</span><span class=\"si\">}</span><span class=\"s2\">:</span><span class=\"si\">{</span><span class=\"n\">port</span><span class=\"si\">}</span><span class=\"s2\">&quot;</span><span class=\"p\">})</span>\n</span><span id=\"__span-2-7\"><a id=\"__codelineno-2-7\" name=\"__codelineno-2-7\" href=\"#__codelineno-2-7\"></a>        <span class=\"n\">topics</span> <span class=\"o\">=</span> <span class=\"n\">admin</span><span class=\"o\">.</span><span class=\"n\">list_topics</span><span class=\"p\">(</span><span class=\"n\">timeout</span><span class=\"o\">=</span><span class=\"mi\">5</span><span class=\"p\">)</span>\n</span><span id=\"__span-2-8\"><a id=\"__codelineno-2-8\" name=\"__codelineno-2-8\" href=\"#__codelineno-2-8\"></a>        <span class=\"c1\"># Check if all required topics are present</span>\n</span><span id=\"__span-2-9\"><a id=\"__codelineno-2-9\" name=\"__codelineno-2-9\" href=\"#__codelineno-2-9\"></a>        <span class=\"k\">if</span> <span class=\"nb\">all</span><span class=\"p\">(</span><span class=\"n\">topic</span> <span class=\"ow\">in</span> <span class=\"n\">topics</span><span class=\"o\">.</span><span class=\"n\">topics</span> <span class=\"k\">for</span> <span class=\"n\">topic</span> <span class=\"ow\">in</span> <span class=\"n\">required_topics</span><span class=\"p\">):</span>\n</span><span id=\"__span-2-10\"><a id=\"__codelineno-2-10\" name=\"__codelineno-2-10\" href=\"#__codelineno-2-10\"></a>            <span class=\"k\">return</span> <span class=\"kc\">True</span>\n</span><span id=\"__span-2-11\"><a id=\"__codelineno-2-11\" name=\"__codelineno-2-11\" href=\"#__codelineno-2-11\"></a>        <span class=\"k\">else</span><span class=\"p\">:</span>\n</span><span id=\"__span-2-12\"><a id=\"__codelineno-2-12\" name=\"__codelineno-2-12\" href=\"#__codelineno-2-12\"></a>            <span class=\"k\">return</span> <span class=\"kc\">False</span>\n</span><span id=\"__span-2-13\"><a id=\"__codelineno-2-13\" name=\"__codelineno-2-13\" href=\"#__codelineno-2-13\"></a>    <span class=\"k\">except</span> <span class=\"n\">KafkaException</span><span class=\"p\">:</span>\n</span><span id=\"__span-2-14\"><a id=\"__codelineno-2-14\" name=\"__codelineno-2-14\" href=\"#__codelineno-2-14\"></a>        <span class=\"k\">return</span> <span class=\"kc\">False</span>\n</span><span id=\"__span-2-15\"><a id=\"__codelineno-2-15\" name=\"__codelineno-2-15\" href=\"#__codelineno-2-15\"></a>\n</span><span id=\"__span-2-16\"><a id=\"__codelineno-2-16\" name=\"__codelineno-2-16\" href=\"#__codelineno-2-16\"></a>\n</span><span id=\"__span-2-17\"><a id=\"__codelineno-2-17\" name=\"__codelineno-2-17\" href=\"#__codelineno-2-17\"></a><span class=\"nd\">@pytest</span><span class=\"o\">.</span><span class=\"n\">fixture</span><span class=\"p\">(</span><span class=\"n\">scope</span><span class=\"o\">=</span><span class=\"s2\">&quot;session&quot;</span><span class=\"p\">)</span>\n</span><span id=\"__span-2-18\"><a id=\"__codelineno-2-18\" name=\"__codelineno-2-18\" href=\"#__codelineno-2-18\"></a><span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">kafka_url</span><span class=\"p\">(</span><span class=\"n\">docker_services</span><span class=\"p\">):</span>\n</span><span id=\"__span-2-19\"><a id=\"__codelineno-2-19\" name=\"__codelineno-2-19\" href=\"#__codelineno-2-19\"></a><span class=\"w\">    </span><span class=\"sd\">&quot;&quot;&quot;Start kafka service and return the url.&quot;&quot;&quot;</span>\n</span><span id=\"__span-2-20\"><a id=\"__codelineno-2-20\" name=\"__codelineno-2-20\" href=\"#__codelineno-2-20\"></a>    <span class=\"n\">port</span> <span class=\"o\">=</span> <span class=\"n\">docker_services</span><span class=\"o\">.</span><span class=\"n\">port_for</span><span class=\"p\">(</span><span class=\"s2\">&quot;kafka&quot;</span><span class=\"p\">,</span> <span class=\"mi\">9092</span><span class=\"p\">)</span>\n</span><span id=\"__span-2-21\"><a id=\"__codelineno-2-21\" name=\"__codelineno-2-21\" href=\"#__codelineno-2-21\"></a>    <span class=\"n\">required_topics</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"s2\">&quot;testtopic&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;input_test_topic&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;output_test_topic&quot;</span><span class=\"p\">]</span>\n</span><span id=\"__span-2-22\"><a id=\"__codelineno-2-22\" name=\"__codelineno-2-22\" href=\"#__codelineno-2-22\"></a>    <span class=\"n\">docker_services</span><span class=\"o\">.</span><span class=\"n\">wait_until_responsive</span><span class=\"p\">(</span>\n</span><span id=\"__span-2-23\"><a id=\"__codelineno-2-23\" name=\"__codelineno-2-23\" href=\"#__codelineno-2-23\"></a>        <span class=\"n\">check</span><span class=\"o\">=</span><span class=\"k\">lambda</span><span class=\"p\">:</span> <span class=\"n\">check_kafka_ready</span><span class=\"p\">(</span><span class=\"n\">port</span><span class=\"o\">=</span><span class=\"n\">port</span><span class=\"p\">,</span> <span class=\"n\">required_topics</span><span class=\"o\">=</span><span class=\"n\">required_topics</span><span class=\"p\">),</span>\n</span><span id=\"__span-2-24\"><a id=\"__codelineno-2-24\" name=\"__codelineno-2-24\" href=\"#__codelineno-2-24\"></a>        <span class=\"n\">timeout</span><span class=\"o\">=</span><span class=\"mf\">30.0</span><span class=\"p\">,</span>\n</span><span id=\"__span-2-25\"><a id=\"__codelineno-2-25\" name=\"__codelineno-2-25\" href=\"#__codelineno-2-25\"></a>        <span class=\"n\">pause</span><span class=\"o\">=</span><span class=\"mf\">0.1</span><span class=\"p\">,</span>\n</span><span id=\"__span-2-26\"><a id=\"__codelineno-2-26\" name=\"__codelineno-2-26\" href=\"#__codelineno-2-26\"></a>    <span class=\"p\">)</span>\n</span><span id=\"__span-2-27\"><a id=\"__codelineno-2-27\" name=\"__codelineno-2-27\" href=\"#__codelineno-2-27\"></a>    <span class=\"k\">return</span> <span class=\"sa\">f</span><span class=\"s2\">&quot;localhost:</span><span class=\"si\">{</span><span class=\"n\">port</span><span class=\"si\">}</span><span class=\"s2\">&quot;</span>\n</span></code></pre></div>\n<p>And finally, a test file to test the <code>KafkaQueueService</code> class:</p>\n<div class=\"language-python highlight\"><pre><span></span><code><span id=\"__span-3-1\"><a id=\"__codelineno-3-1\" name=\"__codelineno-3-1\" href=\"#__codelineno-3-1\"></a><span class=\"nd\">@pytest</span><span class=\"o\">.</span><span class=\"n\">mark</span><span class=\"o\">.</span><span class=\"n\">kafka</span>\n</span><span id=\"__span-3-2\"><a id=\"__codelineno-3-2\" name=\"__codelineno-3-2\" href=\"#__codelineno-3-2\"></a><span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">test_kafka_queue_can_publish_and_consume</span><span class=\"p\">(</span><span class=\"n\">kafka_url</span><span class=\"p\">):</span>\n</span><span id=\"__span-3-3\"><a id=\"__codelineno-3-3\" name=\"__codelineno-3-3\" href=\"#__codelineno-3-3\"></a>    <span class=\"n\">kafka_queue_service</span> <span class=\"o\">=</span> <span class=\"n\">KafkaQueueService</span><span class=\"p\">(</span>\n</span><span id=\"__span-3-4\"><a id=\"__codelineno-3-4\" name=\"__codelineno-3-4\" href=\"#__codelineno-3-4\"></a>        <span class=\"n\">broker</span><span class=\"o\">=</span><span class=\"n\">kafka_url</span><span class=\"p\">,</span>\n</span><span id=\"__span-3-5\"><a id=\"__codelineno-3-5\" name=\"__codelineno-3-5\" href=\"#__codelineno-3-5\"></a>        <span class=\"n\">topic</span><span class=\"o\">=</span><span class=\"s2\">&quot;testtopic&quot;</span><span class=\"p\">,</span>\n</span><span id=\"__span-3-6\"><a id=\"__codelineno-3-6\" name=\"__codelineno-3-6\" href=\"#__codelineno-3-6\"></a>        <span class=\"n\">group_id</span><span class=\"o\">=</span><span class=\"s2\">&quot;testgroup&quot;</span><span class=\"p\">,</span>\n</span><span id=\"__span-3-7\"><a id=\"__codelineno-3-7\" name=\"__codelineno-3-7\" href=\"#__codelineno-3-7\"></a>    <span class=\"p\">)</span>\n</span><span id=\"__span-3-8\"><a id=\"__codelineno-3-8\" name=\"__codelineno-3-8\" href=\"#__codelineno-3-8\"></a>    <span class=\"n\">clear_messages_from_queue</span><span class=\"p\">(</span><span class=\"n\">kafka_queue_service</span><span class=\"p\">)</span>\n</span><span id=\"__span-3-9\"><a id=\"__codelineno-3-9\" name=\"__codelineno-3-9\" href=\"#__codelineno-3-9\"></a>\n</span><span id=\"__span-3-10\"><a id=\"__codelineno-3-10\" name=\"__codelineno-3-10\" href=\"#__codelineno-3-10\"></a>    <span class=\"n\">unique_message</span> <span class=\"o\">=</span> <span class=\"s2\">&quot;hello&quot;</span> <span class=\"o\">+</span> <span class=\"nb\">str</span><span class=\"p\">(</span><span class=\"n\">uuid</span><span class=\"o\">.</span><span class=\"n\">uuid4</span><span class=\"p\">())</span>\n</span><span id=\"__span-3-11\"><a id=\"__codelineno-3-11\" name=\"__codelineno-3-11\" href=\"#__codelineno-3-11\"></a>    <span class=\"n\">kafka_queue_service</span><span class=\"o\">.</span><span class=\"n\">publish</span><span class=\"p\">(</span><span class=\"n\">unique_message</span><span class=\"p\">)</span>\n</span><span id=\"__span-3-12\"><a id=\"__codelineno-3-12\" name=\"__codelineno-3-12\" href=\"#__codelineno-3-12\"></a>\n</span><span id=\"__span-3-13\"><a id=\"__codelineno-3-13\" name=\"__codelineno-3-13\" href=\"#__codelineno-3-13\"></a>    <span class=\"n\">received_message</span> <span class=\"o\">=</span> <span class=\"n\">kafka_queue_service</span><span class=\"o\">.</span><span class=\"n\">consume</span><span class=\"p\">()</span>\n</span><span id=\"__span-3-14\"><a id=\"__codelineno-3-14\" name=\"__codelineno-3-14\" href=\"#__codelineno-3-14\"></a>    <span class=\"k\">assert</span> <span class=\"n\">received_message</span> <span class=\"o\">==</span> <span class=\"n\">unique_message</span>\n</span></code></pre></div>\n<p>Now I can test my <code>KafkaQueueService</code> class without needing a Kafka cluster. This even works on my CI/CD pipeline in Azure DevOps.</p>\n<p>NOTE: The <code>docker-services</code> fixture starts ALL the docker services in the <code>docker-compose.yml</code> file.</p>\n<h2 id=\"bonus-the-passing-implementation-of-kafkaqueueservice\">Bonus: The passing implementation of <code>KafkaQueueService</code></h2>\n<p>This passes the test above (and a few other tests I wrote):</p>\n<div class=\"language-python highlight\"><pre><span></span><code><span id=\"__span-4-1\"><a id=\"__codelineno-4-1\" name=\"__codelineno-4-1\" href=\"#__codelineno-4-1\"></a><span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">confluent_kafka</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">Consumer</span><span class=\"p\">,</span> <span class=\"n\">KafkaError</span><span class=\"p\">,</span> <span class=\"n\">Producer</span>\n</span><span id=\"__span-4-2\"><a id=\"__codelineno-4-2\" name=\"__codelineno-4-2\" href=\"#__codelineno-4-2\"></a>\n</span><span id=\"__span-4-3\"><a id=\"__codelineno-4-3\" name=\"__codelineno-4-3\" href=\"#__codelineno-4-3\"></a><span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">KafkaQueueService</span><span class=\"p\">(</span><span class=\"n\">BaseQueueService</span><span class=\"p\">):</span>\n</span><span id=\"__span-4-4\"><a id=\"__codelineno-4-4\" name=\"__codelineno-4-4\" href=\"#__codelineno-4-4\"></a>    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"fm\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">broker</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"n\">topic</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"n\">group_id</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">):</span>\n</span><span id=\"__span-4-5\"><a id=\"__codelineno-4-5\" name=\"__codelineno-4-5\" href=\"#__codelineno-4-5\"></a>        <span class=\"c1\"># Configuration for the producer and consumer</span>\n</span><span id=\"__span-4-6\"><a id=\"__codelineno-4-6\" name=\"__codelineno-4-6\" href=\"#__codelineno-4-6\"></a>        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">topic</span> <span class=\"o\">=</span> <span class=\"n\">topic</span>\n</span><span id=\"__span-4-7\"><a id=\"__codelineno-4-7\" name=\"__codelineno-4-7\" href=\"#__codelineno-4-7\"></a>        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">producer</span><span class=\"p\">:</span> <span class=\"n\">Producer</span> <span class=\"o\">=</span> <span class=\"n\">Producer</span><span class=\"p\">({</span><span class=\"s2\">&quot;bootstrap.servers&quot;</span><span class=\"p\">:</span> <span class=\"n\">broker</span><span class=\"p\">})</span>\n</span><span id=\"__span-4-8\"><a id=\"__codelineno-4-8\" name=\"__codelineno-4-8\" href=\"#__codelineno-4-8\"></a>        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">consumer</span><span class=\"p\">:</span> <span class=\"n\">Consumer</span> <span class=\"o\">=</span> <span class=\"n\">Consumer</span><span class=\"p\">(</span>\n</span><span id=\"__span-4-9\"><a id=\"__codelineno-4-9\" name=\"__codelineno-4-9\" href=\"#__codelineno-4-9\"></a>            <span class=\"p\">{</span>\n</span><span id=\"__span-4-10\"><a id=\"__codelineno-4-10\" name=\"__codelineno-4-10\" href=\"#__codelineno-4-10\"></a>                <span class=\"s2\">&quot;bootstrap.servers&quot;</span><span class=\"p\">:</span> <span class=\"n\">broker</span><span class=\"p\">,</span>\n</span><span id=\"__span-4-11\"><a id=\"__codelineno-4-11\" name=\"__codelineno-4-11\" href=\"#__codelineno-4-11\"></a>                <span class=\"s2\">&quot;group.id&quot;</span><span class=\"p\">:</span> <span class=\"n\">group_id</span><span class=\"p\">,</span>\n</span><span id=\"__span-4-12\"><a id=\"__codelineno-4-12\" name=\"__codelineno-4-12\" href=\"#__codelineno-4-12\"></a>                <span class=\"s2\">&quot;auto.offset.reset&quot;</span><span class=\"p\">:</span> <span class=\"s2\">&quot;earliest&quot;</span><span class=\"p\">,</span>\n</span><span id=\"__span-4-13\"><a id=\"__codelineno-4-13\" name=\"__codelineno-4-13\" href=\"#__codelineno-4-13\"></a>                <span class=\"s2\">&quot;enable.partition.eof&quot;</span><span class=\"p\">:</span> <span class=\"s2\">&quot;true&quot;</span><span class=\"p\">,</span>\n</span><span id=\"__span-4-14\"><a id=\"__codelineno-4-14\" name=\"__codelineno-4-14\" href=\"#__codelineno-4-14\"></a>            <span class=\"p\">}</span>\n</span><span id=\"__span-4-15\"><a id=\"__codelineno-4-15\" name=\"__codelineno-4-15\" href=\"#__codelineno-4-15\"></a>        <span class=\"p\">)</span>\n</span><span id=\"__span-4-16\"><a id=\"__codelineno-4-16\" name=\"__codelineno-4-16\" href=\"#__codelineno-4-16\"></a>        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">consumer</span><span class=\"o\">.</span><span class=\"n\">subscribe</span><span class=\"p\">([</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">topic</span><span class=\"p\">])</span>\n</span><span id=\"__span-4-17\"><a id=\"__codelineno-4-17\" name=\"__codelineno-4-17\" href=\"#__codelineno-4-17\"></a>\n</span><span id=\"__span-4-18\"><a id=\"__codelineno-4-18\" name=\"__codelineno-4-18\" href=\"#__codelineno-4-18\"></a>    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">publish</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">message</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"kc\">None</span><span class=\"p\">:</span>\n</span><span id=\"__span-4-19\"><a id=\"__codelineno-4-19\" name=\"__codelineno-4-19\" href=\"#__codelineno-4-19\"></a><span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;Publish a message to the Kafka topic.&quot;&quot;&quot;</span>\n</span><span id=\"__span-4-20\"><a id=\"__codelineno-4-20\" name=\"__codelineno-4-20\" href=\"#__codelineno-4-20\"></a>        <span class=\"n\">logger</span><span class=\"o\">.</span><span class=\"n\">debug</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s2\">&quot;Publishing message to topic </span><span class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">topic</span><span class=\"si\">}</span><span class=\"s2\">: </span><span class=\"si\">{</span><span class=\"n\">message</span><span class=\"si\">}</span><span class=\"s2\">&quot;</span><span class=\"p\">)</span>\n</span><span id=\"__span-4-21\"><a id=\"__codelineno-4-21\" name=\"__codelineno-4-21\" href=\"#__codelineno-4-21\"></a>\n</span><span id=\"__span-4-22\"><a id=\"__codelineno-4-22\" name=\"__codelineno-4-22\" href=\"#__codelineno-4-22\"></a>        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">producer</span><span class=\"o\">.</span><span class=\"n\">produce</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">topic</span><span class=\"p\">,</span> <span class=\"n\">message</span><span class=\"o\">.</span><span class=\"n\">encode</span><span class=\"p\">(</span><span class=\"s2\">&quot;utf-8&quot;</span><span class=\"p\">))</span>\n</span><span id=\"__span-4-23\"><a id=\"__codelineno-4-23\" name=\"__codelineno-4-23\" href=\"#__codelineno-4-23\"></a>        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">producer</span><span class=\"o\">.</span><span class=\"n\">flush</span><span class=\"p\">()</span>\n</span><span id=\"__span-4-24\"><a id=\"__codelineno-4-24\" name=\"__codelineno-4-24\" href=\"#__codelineno-4-24\"></a>\n</span><span id=\"__span-4-25\"><a id=\"__codelineno-4-25\" name=\"__codelineno-4-25\" href=\"#__codelineno-4-25\"></a>    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">consume</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">str</span> <span class=\"o\">|</span> <span class=\"kc\">None</span><span class=\"p\">:</span>\n</span><span id=\"__span-4-26\"><a id=\"__codelineno-4-26\" name=\"__codelineno-4-26\" href=\"#__codelineno-4-26\"></a><span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;Consume a single message from the Kafka topic.&quot;&quot;&quot;</span>\n</span><span id=\"__span-4-27\"><a id=\"__codelineno-4-27\" name=\"__codelineno-4-27\" href=\"#__codelineno-4-27\"></a>        <span class=\"n\">logger</span><span class=\"o\">.</span><span class=\"n\">debug</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s2\">&quot;Consuming message from topic </span><span class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">topic</span><span class=\"si\">}</span><span class=\"s2\">&quot;</span><span class=\"p\">)</span>\n</span><span id=\"__span-4-28\"><a id=\"__codelineno-4-28\" name=\"__codelineno-4-28\" href=\"#__codelineno-4-28\"></a>\n</span><span id=\"__span-4-29\"><a id=\"__codelineno-4-29\" name=\"__codelineno-4-29\" href=\"#__codelineno-4-29\"></a>        <span class=\"c1\"># Get the next message</span>\n</span><span id=\"__span-4-30\"><a id=\"__codelineno-4-30\" name=\"__codelineno-4-30\" href=\"#__codelineno-4-30\"></a>        <span class=\"n\">message</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">consumer</span><span class=\"o\">.</span><span class=\"n\">poll</span><span class=\"p\">(</span><span class=\"n\">timeout</span><span class=\"o\">=</span><span class=\"mi\">20</span><span class=\"p\">)</span>\n</span><span id=\"__span-4-31\"><a id=\"__codelineno-4-31\" name=\"__codelineno-4-31\" href=\"#__codelineno-4-31\"></a>        <span class=\"k\">if</span> <span class=\"n\">message</span> <span class=\"ow\">is</span> <span class=\"kc\">None</span><span class=\"p\">:</span>\n</span><span id=\"__span-4-32\"><a id=\"__codelineno-4-32\" name=\"__codelineno-4-32\" href=\"#__codelineno-4-32\"></a>            <span class=\"n\">logger</span><span class=\"o\">.</span><span class=\"n\">debug</span><span class=\"p\">(</span><span class=\"s2\">&quot;Consumer poll timeout&quot;</span><span class=\"p\">)</span>\n</span><span id=\"__span-4-33\"><a id=\"__codelineno-4-33\" name=\"__codelineno-4-33\" href=\"#__codelineno-4-33\"></a>            <span class=\"k\">return</span> <span class=\"kc\">None</span>\n</span><span id=\"__span-4-34\"><a id=\"__codelineno-4-34\" name=\"__codelineno-4-34\" href=\"#__codelineno-4-34\"></a>        <span class=\"c1\"># No new message</span>\n</span><span id=\"__span-4-35\"><a id=\"__codelineno-4-35\" name=\"__codelineno-4-35\" href=\"#__codelineno-4-35\"></a>        <span class=\"k\">if</span> <span class=\"n\">message</span><span class=\"o\">.</span><span class=\"n\">error</span><span class=\"p\">()</span> <span class=\"ow\">is</span> <span class=\"ow\">not</span> <span class=\"kc\">None</span> <span class=\"ow\">and</span> <span class=\"n\">message</span><span class=\"o\">.</span><span class=\"n\">error</span><span class=\"p\">()</span><span class=\"o\">.</span><span class=\"n\">code</span><span class=\"p\">()</span> <span class=\"o\">==</span> <span class=\"n\">KafkaError</span><span class=\"o\">.</span><span class=\"n\">_PARTITION_EOF</span><span class=\"p\">:</span>\n</span><span id=\"__span-4-36\"><a id=\"__codelineno-4-36\" name=\"__codelineno-4-36\" href=\"#__codelineno-4-36\"></a>            <span class=\"n\">logger</span><span class=\"o\">.</span><span class=\"n\">debug</span><span class=\"p\">(</span><span class=\"s2\">&quot;No new messages in topic&quot;</span><span class=\"p\">)</span>\n</span><span id=\"__span-4-37\"><a id=\"__codelineno-4-37\" name=\"__codelineno-4-37\" href=\"#__codelineno-4-37\"></a>            <span class=\"k\">return</span> <span class=\"kc\">None</span>\n</span><span id=\"__span-4-38\"><a id=\"__codelineno-4-38\" name=\"__codelineno-4-38\" href=\"#__codelineno-4-38\"></a>        <span class=\"c1\"># Check for errors</span>\n</span><span id=\"__span-4-39\"><a id=\"__codelineno-4-39\" name=\"__codelineno-4-39\" href=\"#__codelineno-4-39\"></a>        <span class=\"k\">if</span> <span class=\"n\">message</span><span class=\"o\">.</span><span class=\"n\">error</span><span class=\"p\">()</span> <span class=\"ow\">is</span> <span class=\"ow\">not</span> <span class=\"kc\">None</span><span class=\"p\">:</span>\n</span><span id=\"__span-4-40\"><a id=\"__codelineno-4-40\" name=\"__codelineno-4-40\" href=\"#__codelineno-4-40\"></a>            <span class=\"k\">raise</span> <span class=\"ne\">Exception</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s2\">&quot;Consumer error: </span><span class=\"si\">{</span><span class=\"n\">message</span><span class=\"o\">.</span><span class=\"n\">error</span><span class=\"p\">()</span><span class=\"si\">}</span><span class=\"s2\">&quot;</span><span class=\"p\">)</span>\n</span><span id=\"__span-4-41\"><a id=\"__codelineno-4-41\" name=\"__codelineno-4-41\" href=\"#__codelineno-4-41\"></a>        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">consumer</span><span class=\"o\">.</span><span class=\"n\">commit</span><span class=\"p\">(</span><span class=\"n\">message</span><span class=\"p\">,</span> <span class=\"n\">asynchronous</span><span class=\"o\">=</span><span class=\"kc\">False</span><span class=\"p\">)</span>\n</span><span id=\"__span-4-42\"><a id=\"__codelineno-4-42\" name=\"__codelineno-4-42\" href=\"#__codelineno-4-42\"></a>        <span class=\"k\">return</span> <span class=\"n\">message</span><span class=\"o\">.</span><span class=\"n\">value</span><span class=\"p\">()</span><span class=\"o\">.</span><span class=\"n\">decode</span><span class=\"p\">(</span><span class=\"s2\">&quot;utf-8&quot;</span><span class=\"p\">)</span>\n</span><span id=\"__span-4-43\"><a id=\"__codelineno-4-43\" name=\"__codelineno-4-43\" href=\"#__codelineno-4-43\"></a>\n</span><span id=\"__span-4-44\"><a id=\"__codelineno-4-44\" name=\"__codelineno-4-44\" href=\"#__codelineno-4-44\"></a>    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"fm\">__repr__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">str</span><span class=\"p\">:</span>\n</span><span id=\"__span-4-45\"><a id=\"__codelineno-4-45\" name=\"__codelineno-4-45\" href=\"#__codelineno-4-45\"></a>        <span class=\"k\">return</span> <span class=\"sa\">f</span><span class=\"s2\">&quot;</span><span class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"vm\">__class__</span><span class=\"o\">.</span><span class=\"vm\">__name__</span><span class=\"si\">}</span><span class=\"s2\">(topic=</span><span class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">topic</span><span class=\"si\">}</span><span class=\"s2\">)&quot;</span>\n</span></code></pre></div>", "image": null, "date_published": "2024-11-13T00:00:00+00:00", "authors": [], "tags": null}, {"id": "https://kplauritzen.dk/2024-10-28-docker-build-remote/", "url": "https://kplauritzen.dk/2024-10-28-docker-build-remote/", "title": "Build docker images on remote Linux VM", "content_html": "<h1 id=\"build-docker-images-on-remote-linux-vm\">Build docker images on remote Linux VM</h1>\n<p><code>TL;DR</code>: Create a Linux VM in the cloud, then create a docker context for it with</p>\n<div class=\"language-bash highlight\"><pre><span></span><code><span id=\"__span-0-1\"><a id=\"__codelineno-0-1\" name=\"__codelineno-0-1\" href=\"#__codelineno-0-1\"></a>docker<span class=\"w\"> </span>context<span class=\"w\"> </span>create<span class=\"w\"> </span>linux-builder<span class=\"w\"> </span>--docker<span class=\"w\"> </span><span class=\"s2\">&quot;host=ssh://username@remote-ip&quot;</span>\n</span></code></pre></div>\n<p>then build your image with</p>\n<div class=\"language-bash highlight\"><pre><span></span><code><span id=\"__span-1-1\"><a id=\"__codelineno-1-1\" name=\"__codelineno-1-1\" href=\"#__codelineno-1-1\"></a>docker<span class=\"w\"> </span>buildx<span class=\"w\"> </span>build<span class=\"w\"> </span>--context<span class=\"w\"> </span>linux-builder<span class=\"w\"> </span>--platform<span class=\"w\"> </span>linux/amd64<span class=\"w\"> </span>-t<span class=\"w\"> </span>my-image<span class=\"w\"> </span>.\n</span></code></pre></div>\n<h2 id=\"problem-building-some-docker-images-on-a-modern-mac-fails\">Problem: Building some Docker images on a modern Mac fails</h2>\n<p>At work, I'm using an M3 Macbook. It's a great machine, but it's not perfect.\nOne issue is that I can't always build Docker images target to <code>linux/amd64</code> on it.</p>\n<p>Recently, I had an issue where I needed to package a Python application in Docker, and one of the dependencies was <code>pytorch</code>.\nI suspect that is where my issue was coming from.</p>\n<p>Building the image on Mac works fine when running it on the same machine, but when I try to run it on a Linux machine, it fails with the following error:</p>\n<div class=\"language-text highlight\"><pre><span></span><code><span id=\"__span-2-1\"><a id=\"__codelineno-2-1\" name=\"__codelineno-2-1\" href=\"#__codelineno-2-1\"></a>exec /app/.venv/bin/python: exec format error\n</span></code></pre></div>\n<p>This indicated that the Python binary was built for the wrong architecture. Luckily, you can specify the target architecture using\nthe <code>--platform</code> flag when building the image.</p>\n<div class=\"language-bash highlight\"><pre><span></span><code><span id=\"__span-3-1\"><a id=\"__codelineno-3-1\" name=\"__codelineno-3-1\" href=\"#__codelineno-3-1\"></a>docker<span class=\"w\"> </span>buildx<span class=\"w\"> </span>build<span class=\"w\"> </span>--platform<span class=\"w\"> </span>linux/amd64<span class=\"w\"> </span>-t<span class=\"w\"> </span>my-image<span class=\"w\"> </span>.\n</span></code></pre></div>\n<p>Unfortunately, this didn't work for me. I suspect that the <code>pytorch</code> dependency was causing the issue. I got the following error:</p>\n<div class=\"language-text highlight\"><pre><span></span><code><span id=\"__span-4-1\"><a id=\"__codelineno-4-1\" name=\"__codelineno-4-1\" href=\"#__codelineno-4-1\"></a>Cannot install nvidia-cublas-cu12.\n</span></code></pre></div>\n<h2 id=\"solution-build-the-image-on-a-remote-linux-vm\">Solution: Build the image on a remote Linux VM</h2>\n<p>To solve this issue, I decided to build the image on a remote x86_64 Linux VM. This way, I can ensure that the image is built for the correct architecture.</p>\n<p>I used an Azure Virtual Machine with an Ubuntu 24.04 image. I enabled \"Auto-shutdown\" at midnight every day to save costs.</p>\n<p>After ssh-ing into the VM, I installed docker and ensured the user was added to the docker group.</p>\n<div class=\"language-bash highlight\"><pre><span></span><code><span id=\"__span-5-1\"><a id=\"__codelineno-5-1\" name=\"__codelineno-5-1\" href=\"#__codelineno-5-1\"></a>curl<span class=\"w\"> </span>-fsSL<span class=\"w\"> </span>https://get.docker.com<span class=\"w\"> </span>-o<span class=\"w\"> </span>get-docker.sh\n</span><span id=\"__span-5-2\"><a id=\"__codelineno-5-2\" name=\"__codelineno-5-2\" href=\"#__codelineno-5-2\"></a>sudo<span class=\"w\"> </span>sh<span class=\"w\"> </span>get-docker.sh\n</span><span id=\"__span-5-3\"><a id=\"__codelineno-5-3\" name=\"__codelineno-5-3\" href=\"#__codelineno-5-3\"></a>sudo<span class=\"w\"> </span>usermod<span class=\"w\"> </span>-aG<span class=\"w\"> </span>docker<span class=\"w\"> </span>azureuser\n</span></code></pre></div>\n<p>Check that the docker daemon is running:</p>\n<div class=\"language-bash highlight\"><pre><span></span><code><span id=\"__span-6-1\"><a id=\"__codelineno-6-1\" name=\"__codelineno-6-1\" href=\"#__codelineno-6-1\"></a>sudo<span class=\"w\"> </span>systemctl<span class=\"w\"> </span>status<span class=\"w\"> </span>docker\n</span></code></pre></div>\n<p>Now, back on my local machine, I created a docker context for the remote VM:</p>\n<div class=\"language-bash highlight\"><pre><span></span><code><span id=\"__span-7-1\"><a id=\"__codelineno-7-1\" name=\"__codelineno-7-1\" href=\"#__codelineno-7-1\"></a>docker<span class=\"w\"> </span>context<span class=\"w\"> </span>create<span class=\"w\"> </span>linux-builder<span class=\"w\"> </span>--docker<span class=\"w\"> </span><span class=\"s2\">&quot;host=ssh://azureuser@remote-ip&quot;</span>\n</span></code></pre></div>\n<p>Now, I can build the image using the context:</p>\n<div class=\"language-bash highlight\"><pre><span></span><code><span id=\"__span-8-1\"><a id=\"__codelineno-8-1\" name=\"__codelineno-8-1\" href=\"#__codelineno-8-1\"></a>docker<span class=\"w\"> </span>buildx<span class=\"w\"> </span>build<span class=\"w\"> </span>--context<span class=\"w\"> </span>linux-builder<span class=\"w\"> </span>--platform<span class=\"w\"> </span>linux/amd64<span class=\"w\"> </span>-t<span class=\"w\"> </span>my-image<span class=\"w\"> </span>.\n</span></code></pre></div>\n<p>I can also enable the context for all future commands:</p>\n<div class=\"language-bash highlight\"><pre><span></span><code><span id=\"__span-9-1\"><a id=\"__codelineno-9-1\" name=\"__codelineno-9-1\" href=\"#__codelineno-9-1\"></a>docker<span class=\"w\"> </span>context<span class=\"w\"> </span>use<span class=\"w\"> </span>linux-builder\n</span></code></pre></div>", "image": null, "date_published": "2024-10-28T00:00:00+00:00", "authors": [], "tags": null}, {"id": "https://kplauritzen.dk/2023-02-26-azure-docker-cache/", "url": "https://kplauritzen.dk/2023-02-26-azure-docker-cache/", "title": "Caching Docker images in Azure DevOps Pipelines", "content_html": "<h1 id=\"caching-docker-images-in-azure-devops-pipelines\">Caching Docker images in Azure DevOps Pipelines</h1>\n<p><code>TL;DR</code>: Go to the <a href=\"#the-pipeline-template\">bottom of the post</a> to see the full Pipeline template.</p>\n<h2 id=\"the-problem\">The problem</h2>\n<p>In the Data Science team at DFDS, we are using Azure DevOps Pipelines to build and deploy our models.\nWe are using Docker containers to package our models, and we are using Azure Pipelines for our CI/CD.</p>\n<p>For most projects we will build the docker images in:</p>\n<ol>\n<li>The pull request: To make sure the docker image can be built and sometimes also to run some tests in the new container.</li>\n<li>After merging to main: To build the final image that will be deployed to production.</li>\n</ol>\n<p>Step 1 usually happens more than once, as issues with a PR will often require multiple iterations of reviews and fixes.\nFor this reason, it is important that the build time is as short as possible. Long feedback loops are not good for productivity.</p>\n<p>So the solution is to cache the docker images between builds. Azure Pipelines even has a <a href=\"https://learn.microsoft.com/en-us/azure/devops/pipelines/release/caching?view=azure-devops#docker-images\">Cache task</a> that claims to help with caching docker builds.\nBut the commands listed on that documentation page have never worked for me.</p>\n<h2 id=\"the-solution\">The solution</h2>\n<p>My brilliant friend <a href=\"https://www.linkedin.com/in/morten-hels/\">Morten Hels</a> came up with a solution that works.\nI'm taking the liberty of writing it down here, but he is the one who deserves the credit.</p>\n<p>Instead of using <code>docker save</code> and <code>docker load</code> for (attempting to) make cached docker layers available, we use <a href=\"https://docs.docker.com/engine/reference/commandline/buildx/\"><code>docker buildx</code></a> to build the image from, and save to, a cache.</p>\n<p>The commend to run is:</p>\n<div class=\"language-bash highlight\"><pre><span></span><code><span id=\"__span-0-1\"><a id=\"__codelineno-0-1\" name=\"__codelineno-0-1\" href=\"#__codelineno-0-1\"></a>docker<span class=\"w\"> </span>buildx<span class=\"w\"> </span>create<span class=\"w\"> </span>--name<span class=\"w\"> </span>builder<span class=\"w\"> </span>--driver<span class=\"w\"> </span>docker-container<span class=\"w\"> </span>--use<span class=\"w\"> </span><span class=\"c1\">#1</span>\n</span><span id=\"__span-0-2\"><a id=\"__codelineno-0-2\" name=\"__codelineno-0-2\" href=\"#__codelineno-0-2\"></a>docker<span class=\"w\"> </span>buildx<span class=\"w\"> </span>build<span class=\"w\"> </span><span class=\"se\">\\ </span><span class=\"w\">                                              </span>\n</span><span id=\"__span-0-3\"><a id=\"__codelineno-0-3\" name=\"__codelineno-0-3\" href=\"#__codelineno-0-3\"></a><span class=\"w\">    </span>--cache-from<span class=\"o\">=</span><span class=\"nv\">type</span><span class=\"o\">=</span>local,src<span class=\"o\">=</span>docker_cache<span class=\"w\"> </span><span class=\"se\">\\ </span><span class=\"w\">                     </span><span class=\"c1\">#2</span>\n</span><span id=\"__span-0-4\"><a id=\"__codelineno-0-4\" name=\"__codelineno-0-4\" href=\"#__codelineno-0-4\"></a><span class=\"w\">    </span>--cache-to<span class=\"o\">=</span><span class=\"nv\">type</span><span class=\"o\">=</span>local,dest<span class=\"o\">=</span>docker_cache,mode<span class=\"o\">=</span>max<span class=\"w\"> </span><span class=\"se\">\\ </span><span class=\"w\">             </span><span class=\"c1\">#3</span>\n</span><span id=\"__span-0-5\"><a id=\"__codelineno-0-5\" name=\"__codelineno-0-5\" href=\"#__codelineno-0-5\"></a><span class=\"w\">    </span>--file<span class=\"w\"> </span>Dockerfile<span class=\"w\"> </span><span class=\"se\">\\ </span><span class=\"w\">                                            </span>\n</span><span id=\"__span-0-6\"><a id=\"__codelineno-0-6\" name=\"__codelineno-0-6\" href=\"#__codelineno-0-6\"></a><span class=\"w\">    </span>--output<span class=\"o\">=</span><span class=\"nv\">type</span><span class=\"o\">=</span>docker,name<span class=\"o\">=</span>myimage<span class=\"w\"> </span><span class=\"se\">\\ </span><span class=\"w\">                            </span><span class=\"c1\">#4</span>\n</span><span id=\"__span-0-7\"><a id=\"__codelineno-0-7\" name=\"__codelineno-0-7\" href=\"#__codelineno-0-7\"></a><span class=\"w\">    </span>.\n</span></code></pre></div>\n<ol>\n<li>Create a new builder, and use it. This is needed to make the <code>--cache-from</code> and <code>--cache-to</code> options available. I'm using the <code>docker-container</code> driver, but there are other options available. This one is just the easiest to set up, both locally and in a pipeline.</li>\n<li>Use the local cache as a source for the build. This will make the build use the cached layers if they are available.</li>\n<li>Save the layers that were used in the build to the local cache. This will make the layers available for the next build.</li>\n<li>Set the <a href=\"https://docs.docker.com/engine/reference/commandline/buildx_build/#output\">output</a> to be a docker image. This is needed to make the image available for the next step in the pipeline, e.g. pushing it to a registry.</li>\n</ol>\n<h2 id=\"the-pipeline-template\">The pipeline template</h2>\n<p>Here is a complete <a href=\"https://learn.microsoft.com/en-us/azure/devops/pipelines/process/templates?view=azure-devops\">pipeline template</a> that you can use in your own pipelines.</p>\n<div class=\"language-yaml highlight\"><span class=\"filename\">templates.yaml</span><pre><span></span><code><span id=\"__span-1-1\"><a id=\"__codelineno-1-1\" name=\"__codelineno-1-1\" href=\"#__codelineno-1-1\"></a><span class=\"nt\">parameters</span><span class=\"p\">:</span>\n</span><span id=\"__span-1-2\"><a id=\"__codelineno-1-2\" name=\"__codelineno-1-2\" href=\"#__codelineno-1-2\"></a><span class=\"w\">  </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">docker_image_name</span>\n</span><span id=\"__span-1-3\"><a id=\"__codelineno-1-3\" name=\"__codelineno-1-3\" href=\"#__codelineno-1-3\"></a><span class=\"w\">    </span><span class=\"nt\">type</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">string</span>\n</span><span id=\"__span-1-4\"><a id=\"__codelineno-1-4\" name=\"__codelineno-1-4\" href=\"#__codelineno-1-4\"></a><span class=\"w\">    </span><span class=\"nt\">displayName</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s\">&#39;The</span><span class=\"nv\"> </span><span class=\"s\">name</span><span class=\"nv\"> </span><span class=\"s\">of</span><span class=\"nv\"> </span><span class=\"s\">the</span><span class=\"nv\"> </span><span class=\"s\">Docker</span><span class=\"nv\"> </span><span class=\"s\">image</span><span class=\"nv\"> </span><span class=\"s\">to</span><span class=\"nv\"> </span><span class=\"s\">build.</span><span class=\"nv\"> </span><span class=\"s\">Example:</span><span class=\"nv\"> </span><span class=\"s\">klaur-testing.&#39;</span>\n</span><span id=\"__span-1-5\"><a id=\"__codelineno-1-5\" name=\"__codelineno-1-5\" href=\"#__codelineno-1-5\"></a><span class=\"w\">  </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">additional_docker_build_args</span>\n</span><span id=\"__span-1-6\"><a id=\"__codelineno-1-6\" name=\"__codelineno-1-6\" href=\"#__codelineno-1-6\"></a><span class=\"w\">    </span><span class=\"nt\">type</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">string</span>\n</span><span id=\"__span-1-7\"><a id=\"__codelineno-1-7\" name=\"__codelineno-1-7\" href=\"#__codelineno-1-7\"></a><span class=\"w\">    </span><span class=\"nt\">default</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s\">&#39;&#39;</span>\n</span><span id=\"__span-1-8\"><a id=\"__codelineno-1-8\" name=\"__codelineno-1-8\" href=\"#__codelineno-1-8\"></a><span class=\"w\">    </span><span class=\"nt\">displayName</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s\">&#39;Additional</span><span class=\"nv\"> </span><span class=\"s\">arguments</span><span class=\"nv\"> </span><span class=\"s\">to</span><span class=\"nv\"> </span><span class=\"s\">pass</span><span class=\"nv\"> </span><span class=\"s\">to</span><span class=\"nv\"> </span><span class=\"s\">the</span><span class=\"nv\"> </span><span class=\"s\">docker</span><span class=\"nv\"> </span><span class=\"s\">build</span><span class=\"nv\"> </span><span class=\"s\">command.</span><span class=\"nv\"> </span><span class=\"s\">Example:</span><span class=\"nv\"> </span><span class=\"s\">--build-arg</span><span class=\"nv\"> </span><span class=\"s\">SOME_ARG=some_value.&#39;</span>\n</span><span id=\"__span-1-9\"><a id=\"__codelineno-1-9\" name=\"__codelineno-1-9\" href=\"#__codelineno-1-9\"></a><span class=\"w\">  </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">dockerfile_path</span>\n</span><span id=\"__span-1-10\"><a id=\"__codelineno-1-10\" name=\"__codelineno-1-10\" href=\"#__codelineno-1-10\"></a><span class=\"w\">    </span><span class=\"nt\">type</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">string</span>\n</span><span id=\"__span-1-11\"><a id=\"__codelineno-1-11\" name=\"__codelineno-1-11\" href=\"#__codelineno-1-11\"></a><span class=\"w\">    </span><span class=\"nt\">default</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s\">&#39;Dockerfile&#39;</span>\n</span><span id=\"__span-1-12\"><a id=\"__codelineno-1-12\" name=\"__codelineno-1-12\" href=\"#__codelineno-1-12\"></a><span class=\"w\">    </span><span class=\"nt\">displayName</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s\">&#39;The</span><span class=\"nv\"> </span><span class=\"s\">path</span><span class=\"nv\"> </span><span class=\"s\">to</span><span class=\"nv\"> </span><span class=\"s\">the</span><span class=\"nv\"> </span><span class=\"s\">Dockerfile</span><span class=\"nv\"> </span><span class=\"s\">to</span><span class=\"nv\"> </span><span class=\"s\">use.</span><span class=\"nv\"> </span><span class=\"s\">Example:</span><span class=\"nv\"> </span><span class=\"s\">Dockerfile.&#39;</span>\n</span><span id=\"__span-1-13\"><a id=\"__codelineno-1-13\" name=\"__codelineno-1-13\" href=\"#__codelineno-1-13\"></a><span class=\"w\">  </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">docker_build_context</span>\n</span><span id=\"__span-1-14\"><a id=\"__codelineno-1-14\" name=\"__codelineno-1-14\" href=\"#__codelineno-1-14\"></a><span class=\"w\">    </span><span class=\"nt\">type</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">string</span>\n</span><span id=\"__span-1-15\"><a id=\"__codelineno-1-15\" name=\"__codelineno-1-15\" href=\"#__codelineno-1-15\"></a><span class=\"w\">    </span><span class=\"nt\">default</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s\">&#39;.&#39;</span>\n</span><span id=\"__span-1-16\"><a id=\"__codelineno-1-16\" name=\"__codelineno-1-16\" href=\"#__codelineno-1-16\"></a><span class=\"w\">    </span><span class=\"nt\">displayName</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s\">&#39;The</span><span class=\"nv\"> </span><span class=\"s\">path</span><span class=\"nv\"> </span><span class=\"s\">to</span><span class=\"nv\"> </span><span class=\"s\">the</span><span class=\"nv\"> </span><span class=\"s\">directory</span><span class=\"nv\"> </span><span class=\"s\">to</span><span class=\"nv\"> </span><span class=\"s\">use</span><span class=\"nv\"> </span><span class=\"s\">as</span><span class=\"nv\"> </span><span class=\"s\">the</span><span class=\"nv\"> </span><span class=\"s\">build</span><span class=\"nv\"> </span><span class=\"s\">context.</span><span class=\"nv\"> </span><span class=\"s\">Example:</span><span class=\"nv\"> </span><span class=\"s\">.&#39;</span>\n</span><span id=\"__span-1-17\"><a id=\"__codelineno-1-17\" name=\"__codelineno-1-17\" href=\"#__codelineno-1-17\"></a>\n</span><span id=\"__span-1-18\"><a id=\"__codelineno-1-18\" name=\"__codelineno-1-18\" href=\"#__codelineno-1-18\"></a><span class=\"nt\">steps</span><span class=\"p\">:</span>\n</span><span id=\"__span-1-19\"><a id=\"__codelineno-1-19\" name=\"__codelineno-1-19\" href=\"#__codelineno-1-19\"></a><span class=\"w\">  </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">task</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">Cache@2</span>\n</span><span id=\"__span-1-20\"><a id=\"__codelineno-1-20\" name=\"__codelineno-1-20\" href=\"#__codelineno-1-20\"></a><span class=\"w\">    </span><span class=\"nt\">displayName</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">Cache Docker layers</span>\n</span><span id=\"__span-1-21\"><a id=\"__codelineno-1-21\" name=\"__codelineno-1-21\" href=\"#__codelineno-1-21\"></a><span class=\"w\">    </span><span class=\"nt\">inputs</span><span class=\"p\">:</span>\n</span><span id=\"__span-1-22\"><a id=\"__codelineno-1-22\" name=\"__codelineno-1-22\" href=\"#__codelineno-1-22\"></a><span class=\"w\">      </span><span class=\"nt\">key</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s\">&#39;&quot;docker&quot;</span><span class=\"nv\"> </span><span class=\"s\">|</span><span class=\"nv\"> </span><span class=\"s\">&quot;$(Agent.OS)&quot;</span><span class=\"nv\"> </span><span class=\"s\">|</span><span class=\"nv\"> </span><span class=\"s\">&quot;${{</span><span class=\"nv\"> </span><span class=\"s\">parameters.docker_image_name</span><span class=\"nv\"> </span><span class=\"s\">}}&quot;</span><span class=\"nv\"> </span><span class=\"s\">|</span><span class=\"nv\"> </span><span class=\"s\">${{</span><span class=\"nv\"> </span><span class=\"s\">parameters.dockerfile_path</span><span class=\"nv\"> </span><span class=\"s\">}}&#39;</span>\n</span><span id=\"__span-1-23\"><a id=\"__codelineno-1-23\" name=\"__codelineno-1-23\" href=\"#__codelineno-1-23\"></a><span class=\"w\">      </span><span class=\"nt\">restoreKeys</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"p p-Indicator\">|</span>\n</span><span id=\"__span-1-24\"><a id=\"__codelineno-1-24\" name=\"__codelineno-1-24\" href=\"#__codelineno-1-24\"></a><span class=\"w\">        </span><span class=\"no\">&quot;docker&quot; | &quot;$(Agent.OS)&quot; | &quot;${{ parameters.docker_image_name }}&quot;</span>\n</span><span id=\"__span-1-25\"><a id=\"__codelineno-1-25\" name=\"__codelineno-1-25\" href=\"#__codelineno-1-25\"></a><span class=\"w\">      </span><span class=\"nt\">path</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">$(Pipeline.Workspace)/docker_cache</span>\n</span><span id=\"__span-1-26\"><a id=\"__codelineno-1-26\" name=\"__codelineno-1-26\" href=\"#__codelineno-1-26\"></a>\n</span><span id=\"__span-1-27\"><a id=\"__codelineno-1-27\" name=\"__codelineno-1-27\" href=\"#__codelineno-1-27\"></a><span class=\"w\">  </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">script</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"p p-Indicator\">|</span>\n</span><span id=\"__span-1-28\"><a id=\"__codelineno-1-28\" name=\"__codelineno-1-28\" href=\"#__codelineno-1-28\"></a><span class=\"w\">      </span><span class=\"no\">docker buildx create --name builder --driver docker-container --use</span>\n</span><span id=\"__span-1-29\"><a id=\"__codelineno-1-29\" name=\"__codelineno-1-29\" href=\"#__codelineno-1-29\"></a><span class=\"w\">      </span><span class=\"no\">docker buildx build \\</span>\n</span><span id=\"__span-1-30\"><a id=\"__codelineno-1-30\" name=\"__codelineno-1-30\" href=\"#__codelineno-1-30\"></a><span class=\"w\">        </span><span class=\"no\">--cache-from=type=local,src=$(Pipeline.Workspace)/docker_cache \\</span>\n</span><span id=\"__span-1-31\"><a id=\"__codelineno-1-31\" name=\"__codelineno-1-31\" href=\"#__codelineno-1-31\"></a><span class=\"w\">        </span><span class=\"no\">--cache-to=type=local,dest=$(Pipeline.Workspace)/docker_cache,mode=max \\</span>\n</span><span id=\"__span-1-32\"><a id=\"__codelineno-1-32\" name=\"__codelineno-1-32\" href=\"#__codelineno-1-32\"></a><span class=\"w\">        </span><span class=\"no\">--file ${{ parameters.dockerfile_path }} \\</span>\n</span><span id=\"__span-1-33\"><a id=\"__codelineno-1-33\" name=\"__codelineno-1-33\" href=\"#__codelineno-1-33\"></a><span class=\"w\">        </span><span class=\"no\">--output=type=docker,name=${{ parameters.docker_image_name }} \\</span>\n</span><span id=\"__span-1-34\"><a id=\"__codelineno-1-34\" name=\"__codelineno-1-34\" href=\"#__codelineno-1-34\"></a><span class=\"w\">        </span><span class=\"no\">${{ parameters.additional_docker_build_args }} ${{ parameters.docker_build_context }}</span>\n</span><span id=\"__span-1-35\"><a id=\"__codelineno-1-35\" name=\"__codelineno-1-35\" href=\"#__codelineno-1-35\"></a><span class=\"w\">    </span><span class=\"nt\">displayName</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">Build Docker image</span>\n</span><span id=\"__span-1-36\"><a id=\"__codelineno-1-36\" name=\"__codelineno-1-36\" href=\"#__codelineno-1-36\"></a><span class=\"w\">    </span><span class=\"nt\">env</span><span class=\"p\">:</span>\n</span><span id=\"__span-1-37\"><a id=\"__codelineno-1-37\" name=\"__codelineno-1-37\" href=\"#__codelineno-1-37\"></a><span class=\"w\">      </span><span class=\"nt\">DOCKER_BUILDKIT</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">1</span>\n</span></code></pre></div>\n<p>If the above yaml is saved in a <code>templates.yaml</code> file, you can use it in your pipeline like this:</p>\n<div class=\"language-yaml highlight\"><span class=\"filename\">azure-pipelines.yml</span><pre><span></span><code><span id=\"__span-2-1\"><a id=\"__codelineno-2-1\" name=\"__codelineno-2-1\" href=\"#__codelineno-2-1\"></a><span class=\"nt\">jobs</span><span class=\"p\">:</span>\n</span><span id=\"__span-2-2\"><a id=\"__codelineno-2-2\" name=\"__codelineno-2-2\" href=\"#__codelineno-2-2\"></a><span class=\"w\">  </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">job</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">BuildDockerImage</span>\n</span><span id=\"__span-2-3\"><a id=\"__codelineno-2-3\" name=\"__codelineno-2-3\" href=\"#__codelineno-2-3\"></a><span class=\"w\">    </span><span class=\"nt\">steps</span><span class=\"p\">:</span>\n</span><span id=\"__span-2-4\"><a id=\"__codelineno-2-4\" name=\"__codelineno-2-4\" href=\"#__codelineno-2-4\"></a><span class=\"w\">      </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">template</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">templates.yaml</span>\n</span><span id=\"__span-2-5\"><a id=\"__codelineno-2-5\" name=\"__codelineno-2-5\" href=\"#__codelineno-2-5\"></a><span class=\"w\">        </span><span class=\"nt\">parameters</span><span class=\"p\">:</span>\n</span><span id=\"__span-2-6\"><a id=\"__codelineno-2-6\" name=\"__codelineno-2-6\" href=\"#__codelineno-2-6\"></a><span class=\"w\">          </span><span class=\"nt\">docker_image_name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s\">&#39;my-image&#39;</span>\n</span><span id=\"__span-2-7\"><a id=\"__codelineno-2-7\" name=\"__codelineno-2-7\" href=\"#__codelineno-2-7\"></a><span class=\"w\">          </span><span class=\"nt\">additional_docker_build_args</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s\">&#39;--build-arg</span><span class=\"nv\"> </span><span class=\"s\">SOME_ARG=some_value&#39;</span>\n</span><span id=\"__span-2-8\"><a id=\"__codelineno-2-8\" name=\"__codelineno-2-8\" href=\"#__codelineno-2-8\"></a><span class=\"w\">          </span><span class=\"nt\">dockerfile_path</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s\">&#39;Dockerfile&#39;</span>\n</span><span id=\"__span-2-9\"><a id=\"__codelineno-2-9\" name=\"__codelineno-2-9\" href=\"#__codelineno-2-9\"></a><span class=\"w\">          </span><span class=\"nt\">docker_build_context</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"s\">&#39;.&#39;</span>\n</span></code></pre></div>\n<h2 id=\"references\">References</h2>\n<ul>\n<li><a href=\"https://www.linkedin.com/in/morten-hels/\">Morten Hels</a> - Great data scientist moonlighting as an excellent data engineer.</li>\n<li><a href=\"https://stackoverflow.com/a/69198252\">Stack Overflow post</a> that Morten claims got him on the right track.</li>\n<li>Docker documentation on <a href=\"https://docs.docker.com/engine/reference/commandline/buildx/\"><code>docker buildx</code></a>.</li>\n</ul>", "image": null, "date_published": "2023-02-26T00:00:00+00:00", "authors": [], "tags": null}, {"id": "https://kplauritzen.dk/2021-08-26-precommit/", "url": "https://kplauritzen.dk/2021-08-26-precommit/", "title": "Use pre-commit to save time and avoid mistakes", "content_html": "<h1 id=\"use-pre-commit-to-save-time-and-avoid-mistakes\">Use pre-commit to save time and avoid mistakes</h1>\n<p>I'm working in a team of data scientists, and most of us don't have a \"proper\" software background. Most here have some sort of natural sciences education and have picked up machine learning and software development along the way.\nThis means that we don't have the same software craftmanship foundation to build from when our ML models need to grow, scale, and change.</p>\n<p>There is a lot of ways to improve in this area, but a simple one to implement for a whole team in one go is to require <code>pre-commit</code> installed in all projects. This is a tool that lets you define a set of checks that are performed on your code every time you make a commit in git (you are using git, right?).</p>\n<h2 id=\"installation\">Installation</h2>\n<p>Make (or copy from <a href=\"#full-setup\">below</a>) a file called <code>.pre-commit-config.yaml</code> and place it in the root of your repository.\nThen</p>\n<div class=\"language-shell highlight\"><pre><span></span><code><span id=\"__span-0-1\"><a id=\"__codelineno-0-1\" name=\"__codelineno-0-1\" href=\"#__codelineno-0-1\"></a>pip<span class=\"w\"> </span>install<span class=\"w\"> </span>pre-commit\n</span><span id=\"__span-0-2\"><a id=\"__codelineno-0-2\" name=\"__codelineno-0-2\" href=\"#__codelineno-0-2\"></a>pre-commit<span class=\"w\"> </span>install\n</span></code></pre></div>\n<h2 id=\"run\">Run</h2>\n<p>Every time you <code>git commit</code> the hooks you have defined in <code>.pre-commit-config.yaml</code> will be run <em>on the changed files</em>.</p>\n<p>If for some reason you want to run the hooks on <em>all files</em> (for instance in your CI/CD) pipeline, you can do</p>\n<div class=\"language-shell highlight\"><pre><span></span><code><span id=\"__span-1-1\"><a id=\"__codelineno-1-1\" name=\"__codelineno-1-1\" href=\"#__codelineno-1-1\"></a>pre-commit<span class=\"w\"> </span>run<span class=\"w\"> </span>--all-files\n</span></code></pre></div>\n<h2 id=\"individual-checks\">Individual checks</h2>\n<h3 id=\"stop-dealing-with-whitespace-diffs-in-your-prs\">Stop dealing with whitespace diffs in your PRs</h3>\n<div class=\"language-yaml highlight\"><pre><span></span><code><span id=\"__span-2-1\"><a id=\"__codelineno-2-1\" name=\"__codelineno-2-1\" href=\"#__codelineno-2-1\"></a><span class=\"p p-Indicator\">-</span><span class=\"w\">   </span><span class=\"nt\">repo</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">https://github.com/pre-commit/pre-commit-hooks</span>\n</span><span id=\"__span-2-2\"><a id=\"__codelineno-2-2\" name=\"__codelineno-2-2\" href=\"#__codelineno-2-2\"></a><span class=\"w\">    </span><span class=\"nt\">rev</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">v3.2.0</span>\n</span><span id=\"__span-2-3\"><a id=\"__codelineno-2-3\" name=\"__codelineno-2-3\" href=\"#__codelineno-2-3\"></a><span class=\"w\">    </span><span class=\"nt\">hooks</span><span class=\"p\">:</span>\n</span><span id=\"__span-2-4\"><a id=\"__codelineno-2-4\" name=\"__codelineno-2-4\" href=\"#__codelineno-2-4\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\">   </span><span class=\"nt\">id</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">end-of-file-fixer</span>\n</span><span id=\"__span-2-5\"><a id=\"__codelineno-2-5\" name=\"__codelineno-2-5\" href=\"#__codelineno-2-5\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\">   </span><span class=\"nt\">id</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">trailing-whitespace</span>\n</span><span id=\"__span-2-6\"><a id=\"__codelineno-2-6\" name=\"__codelineno-2-6\" href=\"#__codelineno-2-6\"></a><span class=\"p p-Indicator\">-</span><span class=\"w\">   </span><span class=\"nt\">repo</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">https://github.com/pycqa/isort</span>\n</span><span id=\"__span-2-7\"><a id=\"__codelineno-2-7\" name=\"__codelineno-2-7\" href=\"#__codelineno-2-7\"></a><span class=\"w\">    </span><span class=\"nt\">rev</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">5.8.0</span>\n</span><span id=\"__span-2-8\"><a id=\"__codelineno-2-8\" name=\"__codelineno-2-8\" href=\"#__codelineno-2-8\"></a><span class=\"w\">    </span><span class=\"nt\">hooks</span><span class=\"p\">:</span>\n</span><span id=\"__span-2-9\"><a id=\"__codelineno-2-9\" name=\"__codelineno-2-9\" href=\"#__codelineno-2-9\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">id</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">isort</span>\n</span><span id=\"__span-2-10\"><a id=\"__codelineno-2-10\" name=\"__codelineno-2-10\" href=\"#__codelineno-2-10\"></a><span class=\"w\">      </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">isort</span>\n</span></code></pre></div>\n<p>The two first hooks fixes small whitespace mistakes. Each file should end with just a newline, and there should be no whitespace at the end of a line.</p>\n<p><a href=\"https://pycqa.github.io/isort/\"><code>isort</code></a> sorts your import statements. It is a minor thing, but it will group imports into 3 groups:</p>\n<ol>\n<li>Included in Python stdlib.</li>\n<li>Third party library.</li>\n<li>Local code.</li>\n</ol>\n<p>There is some setup needed to make it compatible with <code>black</code>. See <a href=\"#full-setup\">Full setup</a> for details.</p>\n<h3 id=\"you-probably-committed-this-by-mistake\">You probably committed this by mistake</h3>\n<div class=\"language-yaml highlight\"><pre><span></span><code><span id=\"__span-3-1\"><a id=\"__codelineno-3-1\" name=\"__codelineno-3-1\" href=\"#__codelineno-3-1\"></a><span class=\"p p-Indicator\">-</span><span class=\"w\">   </span><span class=\"nt\">repo</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">https://github.com/pre-commit/pre-commit-hooks</span>\n</span><span id=\"__span-3-2\"><a id=\"__codelineno-3-2\" name=\"__codelineno-3-2\" href=\"#__codelineno-3-2\"></a><span class=\"w\">    </span><span class=\"nt\">rev</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">v3.2.0</span>\n</span><span id=\"__span-3-3\"><a id=\"__codelineno-3-3\" name=\"__codelineno-3-3\" href=\"#__codelineno-3-3\"></a><span class=\"w\">    </span><span class=\"nt\">hooks</span><span class=\"p\">:</span>\n</span><span id=\"__span-3-4\"><a id=\"__codelineno-3-4\" name=\"__codelineno-3-4\" href=\"#__codelineno-3-4\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\">   </span><span class=\"nt\">id</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">check-ast</span>\n</span><span id=\"__span-3-5\"><a id=\"__codelineno-3-5\" name=\"__codelineno-3-5\" href=\"#__codelineno-3-5\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\">   </span><span class=\"nt\">id</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">check-json</span>\n</span><span id=\"__span-3-6\"><a id=\"__codelineno-3-6\" name=\"__codelineno-3-6\" href=\"#__codelineno-3-6\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\">   </span><span class=\"nt\">id</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">check-yaml</span>\n</span><span id=\"__span-3-7\"><a id=\"__codelineno-3-7\" name=\"__codelineno-3-7\" href=\"#__codelineno-3-7\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\">   </span><span class=\"nt\">id</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">debug-statements</span>\n</span><span id=\"__span-3-8\"><a id=\"__codelineno-3-8\" name=\"__codelineno-3-8\" href=\"#__codelineno-3-8\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\">   </span><span class=\"nt\">id</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">detect-aws-credentials</span>\n</span><span id=\"__span-3-9\"><a id=\"__codelineno-3-9\" name=\"__codelineno-3-9\" href=\"#__codelineno-3-9\"></a><span class=\"w\">        </span><span class=\"nt\">args</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"p p-Indicator\">[</span><span class=\"nv\">--allow-missing-credentials</span><span class=\"p p-Indicator\">]</span>\n</span><span id=\"__span-3-10\"><a id=\"__codelineno-3-10\" name=\"__codelineno-3-10\" href=\"#__codelineno-3-10\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\">   </span><span class=\"nt\">id</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">detect-private-key</span>\n</span><span id=\"__span-3-11\"><a id=\"__codelineno-3-11\" name=\"__codelineno-3-11\" href=\"#__codelineno-3-11\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\">   </span><span class=\"nt\">id</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">check-merge-conflict</span>\n</span><span id=\"__span-3-12\"><a id=\"__codelineno-3-12\" name=\"__codelineno-3-12\" href=\"#__codelineno-3-12\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\">   </span><span class=\"nt\">id</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">check-added-large-files</span>\n</span><span id=\"__span-3-13\"><a id=\"__codelineno-3-13\" name=\"__codelineno-3-13\" href=\"#__codelineno-3-13\"></a><span class=\"w\">        </span><span class=\"nt\">args</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"p p-Indicator\">[</span><span class=\"s\">&#39;--maxkb=3000&#39;</span><span class=\"p p-Indicator\">]</span>\n</span></code></pre></div>\n<p>Here is a bunch of hooks that will</p>\n<ul>\n<li>\n<p>Check if your Python code is valid (avoiding those <code>SyntaxError</code>s that sometimes crop up)</p>\n</li>\n<li>\n<p>Check that json and yaml files can be parsed</p>\n</li>\n<li>\n<p>Check that you don't have any leftover <code>breakpoint()</code> statements from a debugging session.</p>\n</li>\n<li>\n<p>Check that you haven't accidentally committed secrets.</p>\n</li>\n<li>\n<p>Check that you haven't committed an unresolved merge conflict, like leaving</p>\n<div class=\"language-text highlight\"><pre><span></span><code><span id=\"__span-4-1\"><a id=\"__codelineno-4-1\" name=\"__codelineno-4-1\" href=\"#__codelineno-4-1\"></a>&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt; HEAD\n</span></code></pre></div>\n<p>in the file.</p>\n</li>\n<li>\n<p>Check that you haven't committed an unusally large file. If you <em>actually</em> need large files inside your repo, use <a href=\"https://git-lfs.github.com/\">git-lfs</a>.</p>\n</li>\n</ul>\n<h3 id=\"make-jupyter-notebook-diffs-easier-to-deal-with\">Make Jupyter Notebook diffs easier to deal with</h3>\n<div class=\"language-yaml highlight\"><pre><span></span><code><span id=\"__span-5-1\"><a id=\"__codelineno-5-1\" name=\"__codelineno-5-1\" href=\"#__codelineno-5-1\"></a><span class=\"p p-Indicator\">-</span><span class=\"w\">   </span><span class=\"nt\">repo</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">https://github.com/kynan/nbstripout</span>\n</span><span id=\"__span-5-2\"><a id=\"__codelineno-5-2\" name=\"__codelineno-5-2\" href=\"#__codelineno-5-2\"></a><span class=\"w\">    </span><span class=\"nt\">rev</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">0.5.0</span>\n</span><span id=\"__span-5-3\"><a id=\"__codelineno-5-3\" name=\"__codelineno-5-3\" href=\"#__codelineno-5-3\"></a><span class=\"w\">    </span><span class=\"nt\">hooks</span><span class=\"p\">:</span>\n</span><span id=\"__span-5-4\"><a id=\"__codelineno-5-4\" name=\"__codelineno-5-4\" href=\"#__codelineno-5-4\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">id</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">nbstripout</span>\n</span></code></pre></div>\n<p><a href=\"https://github.com/kynan/nbstripout\"><code>nbstripout</code></a> is very useful if you commit a lot of Jupyter Notebooks to your repo. The output cells are saved in the file, so if you are outputting some large plots, each notebook can become quite big.\nIf your notebooks are not just one-off explorations, but you come back to them more than once, this will make the PR diffs much easier to read.</p>\n<p>If that is NOT the case, maybe you don't want or need this one.</p>\n<h3 id=\"stop-arguing-over-code-style\">Stop arguing over code style</h3>\n<div class=\"language-yaml highlight\"><pre><span></span><code><span id=\"__span-6-1\"><a id=\"__codelineno-6-1\" name=\"__codelineno-6-1\" href=\"#__codelineno-6-1\"></a><span class=\"p p-Indicator\">-</span><span class=\"w\">   </span><span class=\"nt\">repo</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">https://github.com/psf/black</span>\n</span><span id=\"__span-6-2\"><a id=\"__codelineno-6-2\" name=\"__codelineno-6-2\" href=\"#__codelineno-6-2\"></a><span class=\"w\">    </span><span class=\"nt\">rev</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">21.7b0</span>\n</span><span id=\"__span-6-3\"><a id=\"__codelineno-6-3\" name=\"__codelineno-6-3\" href=\"#__codelineno-6-3\"></a><span class=\"w\">    </span><span class=\"nt\">hooks</span><span class=\"p\">:</span>\n</span><span id=\"__span-6-4\"><a id=\"__codelineno-6-4\" name=\"__codelineno-6-4\" href=\"#__codelineno-6-4\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\">   </span><span class=\"nt\">id</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">black</span>\n</span><span id=\"__span-6-5\"><a id=\"__codelineno-6-5\" name=\"__codelineno-6-5\" href=\"#__codelineno-6-5\"></a><span class=\"p p-Indicator\">-</span><span class=\"w\">   </span><span class=\"nt\">repo</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">https://gitlab.com/pycqa/flake8</span>\n</span><span id=\"__span-6-6\"><a id=\"__codelineno-6-6\" name=\"__codelineno-6-6\" href=\"#__codelineno-6-6\"></a><span class=\"w\">    </span><span class=\"nt\">rev</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">3.7.9</span>\n</span><span id=\"__span-6-7\"><a id=\"__codelineno-6-7\" name=\"__codelineno-6-7\" href=\"#__codelineno-6-7\"></a><span class=\"w\">    </span><span class=\"nt\">hooks</span><span class=\"p\">:</span>\n</span><span id=\"__span-6-8\"><a id=\"__codelineno-6-8\" name=\"__codelineno-6-8\" href=\"#__codelineno-6-8\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">id</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">flake8</span>\n</span><span id=\"__span-6-9\"><a id=\"__codelineno-6-9\" name=\"__codelineno-6-9\" href=\"#__codelineno-6-9\"></a><span class=\"w\">      </span><span class=\"nt\">additional_dependencies</span><span class=\"p\">:</span>\n</span><span id=\"__span-6-10\"><a id=\"__codelineno-6-10\" name=\"__codelineno-6-10\" href=\"#__codelineno-6-10\"></a><span class=\"w\">          </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">flake8-unused-arguments</span>\n</span></code></pre></div>\n<p><a href=\"https://black.readthedocs.io/en/stable/\"><code>black</code></a> is a code autoformatter. It has opinions on what is good style and bad, and I mostly agree with those opinions. The <em>very</em> cool thing about <code>black</code> is that it does not just find instances where you are not following the style, it can automatically fix your code to follow the style.</p>\n<p><a href=\"https://flake8.pycqa.org/en/latest/\"><code>flake8</code></a> is a linter. It can check more kinds style errors, but it will not fix anything. It can only complain. This is mostly fine, because it is often trivial to fix the issues that <code>flake8</code> raises.</p>\n<p>Both of these tools needs some config to work as desired. See <a href=\"#full-setup\">Full setup</a> for details.</p>\n<h3 id=\"optional-static-type-checking\">Optional static type checking</h3>\n<div class=\"language-yaml highlight\"><pre><span></span><code><span id=\"__span-7-1\"><a id=\"__codelineno-7-1\" name=\"__codelineno-7-1\" href=\"#__codelineno-7-1\"></a><span class=\"p p-Indicator\">-</span><span class=\"w\">   </span><span class=\"nt\">repo</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">https://github.com/pre-commit/mirrors-mypy</span>\n</span><span id=\"__span-7-2\"><a id=\"__codelineno-7-2\" name=\"__codelineno-7-2\" href=\"#__codelineno-7-2\"></a><span class=\"w\">    </span><span class=\"nt\">rev</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">v0.782</span>\n</span><span id=\"__span-7-3\"><a id=\"__codelineno-7-3\" name=\"__codelineno-7-3\" href=\"#__codelineno-7-3\"></a><span class=\"w\">    </span><span class=\"nt\">hooks</span><span class=\"p\">:</span>\n</span><span id=\"__span-7-4\"><a id=\"__codelineno-7-4\" name=\"__codelineno-7-4\" href=\"#__codelineno-7-4\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\">   </span><span class=\"nt\">id</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">mypy</span>\n</span><span id=\"__span-7-5\"><a id=\"__codelineno-7-5\" name=\"__codelineno-7-5\" href=\"#__codelineno-7-5\"></a><span class=\"w\">        </span><span class=\"nt\">args</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"p p-Indicator\">[</span><span class=\"nv\">--ignore-missing-imports</span><span class=\"p p-Indicator\">]</span>\n</span></code></pre></div>\n<p>You can optionally do static typing in Python now.\n<a href=\"http://mypy-lang.org/\"><code>mypy</code></a> is a tool to run static analysis on your python files and it will complain if you are inputting or return types that don't match your typehints.</p>\n<h2 id=\"full-setup\">Full setup</h2>\n<p>If you just want to copy my setup, add these three files to the root of your repo:</p>\n<div class=\"language-yaml highlight\"><span class=\"filename\">.pre-commit-config.yaml</span><pre><span></span><code><span id=\"__span-8-1\"><a id=\"__codelineno-8-1\" name=\"__codelineno-8-1\" href=\"#__codelineno-8-1\"></a><span class=\"nt\">repos</span><span class=\"p\">:</span>\n</span><span id=\"__span-8-2\"><a id=\"__codelineno-8-2\" name=\"__codelineno-8-2\" href=\"#__codelineno-8-2\"></a><span class=\"p p-Indicator\">-</span><span class=\"w\">   </span><span class=\"nt\">repo</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">https://github.com/pre-commit/pre-commit-hooks</span>\n</span><span id=\"__span-8-3\"><a id=\"__codelineno-8-3\" name=\"__codelineno-8-3\" href=\"#__codelineno-8-3\"></a><span class=\"w\">    </span><span class=\"nt\">rev</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">v3.2.0</span>\n</span><span id=\"__span-8-4\"><a id=\"__codelineno-8-4\" name=\"__codelineno-8-4\" href=\"#__codelineno-8-4\"></a><span class=\"w\">    </span><span class=\"nt\">hooks</span><span class=\"p\">:</span>\n</span><span id=\"__span-8-5\"><a id=\"__codelineno-8-5\" name=\"__codelineno-8-5\" href=\"#__codelineno-8-5\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\">   </span><span class=\"nt\">id</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">check-ast</span>\n</span><span id=\"__span-8-6\"><a id=\"__codelineno-8-6\" name=\"__codelineno-8-6\" href=\"#__codelineno-8-6\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\">   </span><span class=\"nt\">id</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">check-json</span>\n</span><span id=\"__span-8-7\"><a id=\"__codelineno-8-7\" name=\"__codelineno-8-7\" href=\"#__codelineno-8-7\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\">   </span><span class=\"nt\">id</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">check-yaml</span>\n</span><span id=\"__span-8-8\"><a id=\"__codelineno-8-8\" name=\"__codelineno-8-8\" href=\"#__codelineno-8-8\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\">   </span><span class=\"nt\">id</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">debug-statements</span>\n</span><span id=\"__span-8-9\"><a id=\"__codelineno-8-9\" name=\"__codelineno-8-9\" href=\"#__codelineno-8-9\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\">   </span><span class=\"nt\">id</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">detect-aws-credentials</span>\n</span><span id=\"__span-8-10\"><a id=\"__codelineno-8-10\" name=\"__codelineno-8-10\" href=\"#__codelineno-8-10\"></a><span class=\"w\">        </span><span class=\"nt\">args</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"p p-Indicator\">[</span><span class=\"nv\">--allow-missing-credentials</span><span class=\"p p-Indicator\">]</span>\n</span><span id=\"__span-8-11\"><a id=\"__codelineno-8-11\" name=\"__codelineno-8-11\" href=\"#__codelineno-8-11\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\">   </span><span class=\"nt\">id</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">detect-private-key</span>\n</span><span id=\"__span-8-12\"><a id=\"__codelineno-8-12\" name=\"__codelineno-8-12\" href=\"#__codelineno-8-12\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\">   </span><span class=\"nt\">id</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">check-merge-conflict</span>\n</span><span id=\"__span-8-13\"><a id=\"__codelineno-8-13\" name=\"__codelineno-8-13\" href=\"#__codelineno-8-13\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\">   </span><span class=\"nt\">id</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">check-added-large-files</span>\n</span><span id=\"__span-8-14\"><a id=\"__codelineno-8-14\" name=\"__codelineno-8-14\" href=\"#__codelineno-8-14\"></a><span class=\"w\">        </span><span class=\"nt\">args</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"p p-Indicator\">[</span><span class=\"s\">&#39;--maxkb=3000&#39;</span><span class=\"p p-Indicator\">]</span>\n</span><span id=\"__span-8-15\"><a id=\"__codelineno-8-15\" name=\"__codelineno-8-15\" href=\"#__codelineno-8-15\"></a><span class=\"p p-Indicator\">-</span><span class=\"w\">   </span><span class=\"nt\">repo</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">https://github.com/pre-commit/mirrors-mypy</span>\n</span><span id=\"__span-8-16\"><a id=\"__codelineno-8-16\" name=\"__codelineno-8-16\" href=\"#__codelineno-8-16\"></a><span class=\"w\">    </span><span class=\"nt\">rev</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">v0.782</span>\n</span><span id=\"__span-8-17\"><a id=\"__codelineno-8-17\" name=\"__codelineno-8-17\" href=\"#__codelineno-8-17\"></a><span class=\"w\">    </span><span class=\"nt\">hooks</span><span class=\"p\">:</span>\n</span><span id=\"__span-8-18\"><a id=\"__codelineno-8-18\" name=\"__codelineno-8-18\" href=\"#__codelineno-8-18\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\">   </span><span class=\"nt\">id</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">mypy</span>\n</span><span id=\"__span-8-19\"><a id=\"__codelineno-8-19\" name=\"__codelineno-8-19\" href=\"#__codelineno-8-19\"></a><span class=\"w\">        </span><span class=\"nt\">args</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"p p-Indicator\">[</span><span class=\"nv\">--ignore-missing-imports</span><span class=\"p p-Indicator\">]</span>\n</span><span id=\"__span-8-20\"><a id=\"__codelineno-8-20\" name=\"__codelineno-8-20\" href=\"#__codelineno-8-20\"></a><span class=\"p p-Indicator\">-</span><span class=\"w\">   </span><span class=\"nt\">repo</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">https://github.com/pycqa/isort</span>\n</span><span id=\"__span-8-21\"><a id=\"__codelineno-8-21\" name=\"__codelineno-8-21\" href=\"#__codelineno-8-21\"></a><span class=\"w\">    </span><span class=\"nt\">rev</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">5.8.0</span>\n</span><span id=\"__span-8-22\"><a id=\"__codelineno-8-22\" name=\"__codelineno-8-22\" href=\"#__codelineno-8-22\"></a><span class=\"w\">    </span><span class=\"nt\">hooks</span><span class=\"p\">:</span>\n</span><span id=\"__span-8-23\"><a id=\"__codelineno-8-23\" name=\"__codelineno-8-23\" href=\"#__codelineno-8-23\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">id</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">isort</span>\n</span><span id=\"__span-8-24\"><a id=\"__codelineno-8-24\" name=\"__codelineno-8-24\" href=\"#__codelineno-8-24\"></a><span class=\"w\">      </span><span class=\"nt\">name</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">isort</span>\n</span><span id=\"__span-8-25\"><a id=\"__codelineno-8-25\" name=\"__codelineno-8-25\" href=\"#__codelineno-8-25\"></a><span class=\"p p-Indicator\">-</span><span class=\"w\">   </span><span class=\"nt\">repo</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">https://github.com/psf/black</span>\n</span><span id=\"__span-8-26\"><a id=\"__codelineno-8-26\" name=\"__codelineno-8-26\" href=\"#__codelineno-8-26\"></a><span class=\"w\">    </span><span class=\"nt\">rev</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">21.7b0</span>\n</span><span id=\"__span-8-27\"><a id=\"__codelineno-8-27\" name=\"__codelineno-8-27\" href=\"#__codelineno-8-27\"></a><span class=\"w\">    </span><span class=\"nt\">hooks</span><span class=\"p\">:</span>\n</span><span id=\"__span-8-28\"><a id=\"__codelineno-8-28\" name=\"__codelineno-8-28\" href=\"#__codelineno-8-28\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\">   </span><span class=\"nt\">id</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">black</span>\n</span><span id=\"__span-8-29\"><a id=\"__codelineno-8-29\" name=\"__codelineno-8-29\" href=\"#__codelineno-8-29\"></a><span class=\"p p-Indicator\">-</span><span class=\"w\">   </span><span class=\"nt\">repo</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">https://gitlab.com/pycqa/flake8</span>\n</span><span id=\"__span-8-30\"><a id=\"__codelineno-8-30\" name=\"__codelineno-8-30\" href=\"#__codelineno-8-30\"></a><span class=\"w\">    </span><span class=\"nt\">rev</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">3.7.9</span>\n</span><span id=\"__span-8-31\"><a id=\"__codelineno-8-31\" name=\"__codelineno-8-31\" href=\"#__codelineno-8-31\"></a><span class=\"w\">    </span><span class=\"nt\">hooks</span><span class=\"p\">:</span>\n</span><span id=\"__span-8-32\"><a id=\"__codelineno-8-32\" name=\"__codelineno-8-32\" href=\"#__codelineno-8-32\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">id</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">flake8</span>\n</span><span id=\"__span-8-33\"><a id=\"__codelineno-8-33\" name=\"__codelineno-8-33\" href=\"#__codelineno-8-33\"></a><span class=\"w\">      </span><span class=\"nt\">additional_dependencies</span><span class=\"p\">:</span>\n</span><span id=\"__span-8-34\"><a id=\"__codelineno-8-34\" name=\"__codelineno-8-34\" href=\"#__codelineno-8-34\"></a><span class=\"w\">          </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">flake8-unused-arguments</span>\n</span><span id=\"__span-8-35\"><a id=\"__codelineno-8-35\" name=\"__codelineno-8-35\" href=\"#__codelineno-8-35\"></a><span class=\"p p-Indicator\">-</span><span class=\"w\">   </span><span class=\"nt\">repo</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">https://github.com/kynan/nbstripout</span>\n</span><span id=\"__span-8-36\"><a id=\"__codelineno-8-36\" name=\"__codelineno-8-36\" href=\"#__codelineno-8-36\"></a><span class=\"w\">    </span><span class=\"nt\">rev</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">0.5.0</span>\n</span><span id=\"__span-8-37\"><a id=\"__codelineno-8-37\" name=\"__codelineno-8-37\" href=\"#__codelineno-8-37\"></a><span class=\"w\">    </span><span class=\"nt\">hooks</span><span class=\"p\">:</span>\n</span><span id=\"__span-8-38\"><a id=\"__codelineno-8-38\" name=\"__codelineno-8-38\" href=\"#__codelineno-8-38\"></a><span class=\"w\">    </span><span class=\"p p-Indicator\">-</span><span class=\"w\"> </span><span class=\"nt\">id</span><span class=\"p\">:</span><span class=\"w\"> </span><span class=\"l l-Scalar l-Scalar-Plain\">nbstripout</span>\n</span></code></pre></div>\n<div class=\"language-toml highlight\"><span class=\"filename\">pyproject.toml</span><pre><span></span><code><span id=\"__span-9-1\"><a id=\"__codelineno-9-1\" name=\"__codelineno-9-1\" href=\"#__codelineno-9-1\"></a><span class=\"k\">[tool.black]</span>\n</span><span id=\"__span-9-2\"><a id=\"__codelineno-9-2\" name=\"__codelineno-9-2\" href=\"#__codelineno-9-2\"></a><span class=\"n\">line-length</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"mi\">100</span>\n</span><span id=\"__span-9-3\"><a id=\"__codelineno-9-3\" name=\"__codelineno-9-3\" href=\"#__codelineno-9-3\"></a><span class=\"n\">include</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"s1\">&#39;\\.pyi?$&#39;</span>\n</span><span id=\"__span-9-4\"><a id=\"__codelineno-9-4\" name=\"__codelineno-9-4\" href=\"#__codelineno-9-4\"></a><span class=\"n\">exclude</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"s1\">&#39;&#39;&#39;</span>\n</span><span id=\"__span-9-5\"><a id=\"__codelineno-9-5\" name=\"__codelineno-9-5\" href=\"#__codelineno-9-5\"></a><span class=\"s1\">/(</span>\n</span><span id=\"__span-9-6\"><a id=\"__codelineno-9-6\" name=\"__codelineno-9-6\" href=\"#__codelineno-9-6\"></a><span class=\"s1\">    \\.git</span>\n</span><span id=\"__span-9-7\"><a id=\"__codelineno-9-7\" name=\"__codelineno-9-7\" href=\"#__codelineno-9-7\"></a><span class=\"s1\">  | \\.hg</span>\n</span><span id=\"__span-9-8\"><a id=\"__codelineno-9-8\" name=\"__codelineno-9-8\" href=\"#__codelineno-9-8\"></a><span class=\"s1\">  | \\.mypy_cache</span>\n</span><span id=\"__span-9-9\"><a id=\"__codelineno-9-9\" name=\"__codelineno-9-9\" href=\"#__codelineno-9-9\"></a><span class=\"s1\">  | \\.tox</span>\n</span><span id=\"__span-9-10\"><a id=\"__codelineno-9-10\" name=\"__codelineno-9-10\" href=\"#__codelineno-9-10\"></a><span class=\"s1\">  | \\.venv</span>\n</span><span id=\"__span-9-11\"><a id=\"__codelineno-9-11\" name=\"__codelineno-9-11\" href=\"#__codelineno-9-11\"></a><span class=\"s1\">  | _build</span>\n</span><span id=\"__span-9-12\"><a id=\"__codelineno-9-12\" name=\"__codelineno-9-12\" href=\"#__codelineno-9-12\"></a><span class=\"s1\">  | buck-out</span>\n</span><span id=\"__span-9-13\"><a id=\"__codelineno-9-13\" name=\"__codelineno-9-13\" href=\"#__codelineno-9-13\"></a><span class=\"s1\">  | build</span>\n</span><span id=\"__span-9-14\"><a id=\"__codelineno-9-14\" name=\"__codelineno-9-14\" href=\"#__codelineno-9-14\"></a><span class=\"s1\">  | dist</span>\n</span><span id=\"__span-9-15\"><a id=\"__codelineno-9-15\" name=\"__codelineno-9-15\" href=\"#__codelineno-9-15\"></a><span class=\"s1\">)/</span>\n</span><span id=\"__span-9-16\"><a id=\"__codelineno-9-16\" name=\"__codelineno-9-16\" href=\"#__codelineno-9-16\"></a><span class=\"s1\">&#39;&#39;&#39;</span>\n</span><span id=\"__span-9-17\"><a id=\"__codelineno-9-17\" name=\"__codelineno-9-17\" href=\"#__codelineno-9-17\"></a>\n</span><span id=\"__span-9-18\"><a id=\"__codelineno-9-18\" name=\"__codelineno-9-18\" href=\"#__codelineno-9-18\"></a><span class=\"k\">[tool.isort]</span>\n</span><span id=\"__span-9-19\"><a id=\"__codelineno-9-19\" name=\"__codelineno-9-19\" href=\"#__codelineno-9-19\"></a><span class=\"n\">profile</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"s2\">&quot;black&quot;</span>\n</span><span id=\"__span-9-20\"><a id=\"__codelineno-9-20\" name=\"__codelineno-9-20\" href=\"#__codelineno-9-20\"></a><span class=\"n\">line_length</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"mi\">100</span>\n</span></code></pre></div>\n<div class=\"language-toml highlight\"><span class=\"filename\">.flake8</span><pre><span></span><code><span id=\"__span-10-1\"><a id=\"__codelineno-10-1\" name=\"__codelineno-10-1\" href=\"#__codelineno-10-1\"></a><span class=\"k\">[flake8]</span>\n</span><span id=\"__span-10-2\"><a id=\"__codelineno-10-2\" name=\"__codelineno-10-2\" href=\"#__codelineno-10-2\"></a><span class=\"n\">ignore</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"err\">E</span><span class=\"mi\">203</span><span class=\"err\">,</span><span class=\"w\"> </span><span class=\"n\">E266</span><span class=\"err\">,</span><span class=\"w\"> </span><span class=\"n\">E501</span><span class=\"err\">,</span><span class=\"w\"> </span><span class=\"n\">W503</span>\n</span><span id=\"__span-10-3\"><a id=\"__codelineno-10-3\" name=\"__codelineno-10-3\" href=\"#__codelineno-10-3\"></a><span class=\"n\">max-line-length</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"mi\">100</span>\n</span><span id=\"__span-10-4\"><a id=\"__codelineno-10-4\" name=\"__codelineno-10-4\" href=\"#__codelineno-10-4\"></a><span class=\"n\">max-complexity</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"mi\">18</span>\n</span><span id=\"__span-10-5\"><a id=\"__codelineno-10-5\" name=\"__codelineno-10-5\" href=\"#__codelineno-10-5\"></a><span class=\"n\">select</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"err\">B,C,E,F,W,T</span><span class=\"mi\">4</span><span class=\"err\">,</span><span class=\"n\">B9</span><span class=\"err\">,</span><span class=\"n\">U100</span>\n</span><span id=\"__span-10-6\"><a id=\"__codelineno-10-6\" name=\"__codelineno-10-6\" href=\"#__codelineno-10-6\"></a><span class=\"n\">unused-arguments-ignore-abstract-functions</span><span class=\"w\"> </span><span class=\"o\">=</span><span class=\"w\"> </span><span class=\"err\">True</span>\n</span></code></pre></div>\n<h2 id=\"updates\">Updates</h2>\n<ul>\n<li>2021-09-08: Add <code>flake8-unused-arguments</code>.</li>\n</ul>", "image": null, "date_published": "2021-08-26T00:00:00+00:00", "authors": [], "tags": null}, {"id": "https://kplauritzen.dk/2021-08-11-convert-dataclasss-np-array/", "url": "https://kplauritzen.dk/2021-08-11-convert-dataclasss-np-array/", "title": "Converting between custom dataclasses and numpy arrays", "content_html": "<h1 id=\"converting-between-custom-dataclasses-and-numpy-arrays\">Converting between custom dataclasses and numpy arrays</h1>\n<p><code>TL;DR</code>: Implement <code>__array__()</code>, <code>__len__()</code> and <code>__getitem__()</code> methods on your <code>dataclass</code>.\nSee <a href=\"#the-real-solution\">the final section</a> for a working example.</p>\n<p>I have gotten increasingly interested in python <a href=\"https://docs.python.org/3/library/typing.html\">typehints</a>,\nand in a recent project I'm creating a lot of custom types to create interfaces for different modules in my application.\nI usually try to keep the types as standardlib python types, but the <code>dataclass</code> can be pretty neat.</p>\n<p>Here is an example of a simple custom dataclass</p>\n<div class=\"language-python highlight\"><pre><span></span><code><span id=\"__span-0-1\"><a id=\"__codelineno-0-1\" name=\"__codelineno-0-1\" href=\"#__codelineno-0-1\"></a><span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">dataclasses</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">dataclass</span>\n</span><span id=\"__span-0-2\"><a id=\"__codelineno-0-2\" name=\"__codelineno-0-2\" href=\"#__codelineno-0-2\"></a><span class=\"nd\">@dataclass</span>\n</span><span id=\"__span-0-3\"><a id=\"__codelineno-0-3\" name=\"__codelineno-0-3\" href=\"#__codelineno-0-3\"></a><span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">Point2D</span><span class=\"p\">:</span>\n</span><span id=\"__span-0-4\"><a id=\"__codelineno-0-4\" name=\"__codelineno-0-4\" href=\"#__codelineno-0-4\"></a>    <span class=\"n\">x</span><span class=\"p\">:</span> <span class=\"nb\">float</span>\n</span><span id=\"__span-0-5\"><a id=\"__codelineno-0-5\" name=\"__codelineno-0-5\" href=\"#__codelineno-0-5\"></a>    <span class=\"n\">y</span><span class=\"p\">:</span> <span class=\"nb\">float</span>\n</span></code></pre></div>\n<p>If I want a simple way to convert this to a <code>numpy</code> array, I run into a few stumbling blocks:</p>\n<h2 id=\"converting-one-instance-to-a-nparray-the-naive-way\">Converting one instance to a np.array (the naive way)</h2>\n<div class=\"language-python highlight\"><pre><span></span><code><span id=\"__span-1-1\"><a id=\"__codelineno-1-1\" name=\"__codelineno-1-1\" href=\"#__codelineno-1-1\"></a><span class=\"kn\">import</span><span class=\"w\"> </span><span class=\"nn\">numpy</span><span class=\"w\"> </span><span class=\"k\">as</span><span class=\"w\"> </span><span class=\"nn\">np</span>\n</span><span id=\"__span-1-2\"><a id=\"__codelineno-1-2\" name=\"__codelineno-1-2\" href=\"#__codelineno-1-2\"></a>\n</span><span id=\"__span-1-3\"><a id=\"__codelineno-1-3\" name=\"__codelineno-1-3\" href=\"#__codelineno-1-3\"></a><span class=\"n\">p</span> <span class=\"o\">=</span> <span class=\"n\">Point2D</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"o\">=</span><span class=\"mf\">0.2</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"o\">=</span><span class=\"mf\">3.0</span><span class=\"p\">)</span>\n</span><span id=\"__span-1-4\"><a id=\"__codelineno-1-4\" name=\"__codelineno-1-4\" href=\"#__codelineno-1-4\"></a><span class=\"n\">arr</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"o\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">p</span><span class=\"p\">)</span>\n</span><span id=\"__span-1-5\"><a id=\"__codelineno-1-5\" name=\"__codelineno-1-5\" href=\"#__codelineno-1-5\"></a><span class=\"nb\">print</span><span class=\"p\">(</span><span class=\"n\">arr</span><span class=\"p\">,</span> <span class=\"nb\">type</span><span class=\"p\">(</span><span class=\"n\">arr</span><span class=\"p\">),</span> <span class=\"n\">arr</span><span class=\"o\">.</span><span class=\"n\">dtype</span><span class=\"p\">)</span>\n</span><span id=\"__span-1-6\"><a id=\"__codelineno-1-6\" name=\"__codelineno-1-6\" href=\"#__codelineno-1-6\"></a><span class=\"c1\"># Point2D(x=0.2, y=3.0) &lt;class &#39;numpy.ndarray&#39;&gt; object</span>\n</span></code></pre></div>\n<p>I don't get the <em>values</em> from <code>Point2D</code>, I just get an array with the object inside.\nHowever, we can implement an <code>__array__</code> method on <code>Point2D</code> that will allow numpy to produce an array with the correct dtype.</p>\n<div class=\"language-python highlight\"><pre><span></span><code><span id=\"__span-2-1\"><a id=\"__codelineno-2-1\" name=\"__codelineno-2-1\" href=\"#__codelineno-2-1\"></a><span class=\"nd\">@dataclass</span>\n</span><span id=\"__span-2-2\"><a id=\"__codelineno-2-2\" name=\"__codelineno-2-2\" href=\"#__codelineno-2-2\"></a><span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">Point2D</span><span class=\"p\">:</span>\n</span><span id=\"__span-2-3\"><a id=\"__codelineno-2-3\" name=\"__codelineno-2-3\" href=\"#__codelineno-2-3\"></a>    <span class=\"o\">...</span>\n</span><span id=\"__span-2-4\"><a id=\"__codelineno-2-4\" name=\"__codelineno-2-4\" href=\"#__codelineno-2-4\"></a>    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">__array__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">):</span>\n</span><span id=\"__span-2-5\"><a id=\"__codelineno-2-5\" name=\"__codelineno-2-5\" href=\"#__codelineno-2-5\"></a>        <span class=\"k\">return</span> <span class=\"n\">np</span><span class=\"o\">.</span><span class=\"n\">array</span><span class=\"p\">([</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">y</span><span class=\"p\">])</span>\n</span></code></pre></div>\n<p>Now we get a much more sensible result when converting</p>\n<div class=\"language-python highlight\"><pre><span></span><code><span id=\"__span-3-1\"><a id=\"__codelineno-3-1\" name=\"__codelineno-3-1\" href=\"#__codelineno-3-1\"></a><span class=\"n\">p</span> <span class=\"o\">=</span> <span class=\"n\">Point2D</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"o\">=</span><span class=\"mf\">0.2</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"o\">=</span><span class=\"mf\">3.0</span><span class=\"p\">)</span>\n</span><span id=\"__span-3-2\"><a id=\"__codelineno-3-2\" name=\"__codelineno-3-2\" href=\"#__codelineno-3-2\"></a><span class=\"n\">arr</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"o\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">p</span><span class=\"p\">)</span>\n</span><span id=\"__span-3-3\"><a id=\"__codelineno-3-3\" name=\"__codelineno-3-3\" href=\"#__codelineno-3-3\"></a><span class=\"nb\">print</span><span class=\"p\">(</span><span class=\"n\">arr</span><span class=\"p\">,</span> <span class=\"nb\">type</span><span class=\"p\">(</span><span class=\"n\">arr</span><span class=\"p\">),</span> <span class=\"n\">arr</span><span class=\"o\">.</span><span class=\"n\">dtype</span><span class=\"p\">)</span>\n</span><span id=\"__span-3-4\"><a id=\"__codelineno-3-4\" name=\"__codelineno-3-4\" href=\"#__codelineno-3-4\"></a><span class=\"c1\"># [0.2 3. ] &lt;class &#39;numpy.ndarray&#39;&gt; float64</span>\n</span></code></pre></div>\n<p>The trouble comes when we want to make a new custom type that inherits from <code>Point2D</code>.</p>\n<h2 id=\"inheriting-the-__array__-method\">Inheriting the <code>__array__</code> method</h2>\n<p>Let's make a simple extension of <code>Point2D</code> to 3 dimensions</p>\n<div class=\"language-python highlight\"><pre><span></span><code><span id=\"__span-4-1\"><a id=\"__codelineno-4-1\" name=\"__codelineno-4-1\" href=\"#__codelineno-4-1\"></a><span class=\"nd\">@dataclass</span>\n</span><span id=\"__span-4-2\"><a id=\"__codelineno-4-2\" name=\"__codelineno-4-2\" href=\"#__codelineno-4-2\"></a><span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">Point3D</span><span class=\"p\">(</span><span class=\"n\">Point2D</span><span class=\"p\">):</span>\n</span><span id=\"__span-4-3\"><a id=\"__codelineno-4-3\" name=\"__codelineno-4-3\" href=\"#__codelineno-4-3\"></a>    <span class=\"n\">z</span><span class=\"p\">:</span> <span class=\"nb\">float</span>\n</span></code></pre></div>\n<p>If we try to convert this into a numpy array, we run into trouble</p>\n<div class=\"language-python highlight\"><pre><span></span><code><span id=\"__span-5-1\"><a id=\"__codelineno-5-1\" name=\"__codelineno-5-1\" href=\"#__codelineno-5-1\"></a><span class=\"n\">p</span> <span class=\"o\">=</span> <span class=\"n\">Point3D</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"o\">=</span><span class=\"mf\">0.2</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"o\">=</span><span class=\"mf\">3.0</span><span class=\"p\">,</span> <span class=\"n\">z</span><span class=\"o\">=-</span><span class=\"mf\">1.0</span><span class=\"p\">)</span>\n</span><span id=\"__span-5-2\"><a id=\"__codelineno-5-2\" name=\"__codelineno-5-2\" href=\"#__codelineno-5-2\"></a><span class=\"n\">arr</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"o\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">p</span><span class=\"p\">)</span>\n</span><span id=\"__span-5-3\"><a id=\"__codelineno-5-3\" name=\"__codelineno-5-3\" href=\"#__codelineno-5-3\"></a><span class=\"nb\">print</span><span class=\"p\">(</span><span class=\"n\">arr</span><span class=\"p\">,</span> <span class=\"nb\">type</span><span class=\"p\">(</span><span class=\"n\">arr</span><span class=\"p\">),</span> <span class=\"n\">arr</span><span class=\"o\">.</span><span class=\"n\">dtype</span><span class=\"p\">)</span>\n</span><span id=\"__span-5-4\"><a id=\"__codelineno-5-4\" name=\"__codelineno-5-4\" href=\"#__codelineno-5-4\"></a><span class=\"c1\"># [0.2 3. ] &lt;class &#39;numpy.ndarray&#39;&gt; float64</span>\n</span></code></pre></div>\n<p>We are missing the new z dimension!</p>\n<p>One fix is to make a new <code>__array__</code> method.</p>\n<div class=\"language-python highlight\"><pre><span></span><code><span id=\"__span-6-1\"><a id=\"__codelineno-6-1\" name=\"__codelineno-6-1\" href=\"#__codelineno-6-1\"></a><span class=\"nd\">@dataclass</span>\n</span><span id=\"__span-6-2\"><a id=\"__codelineno-6-2\" name=\"__codelineno-6-2\" href=\"#__codelineno-6-2\"></a><span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">Point3D</span><span class=\"p\">(</span><span class=\"n\">Point2D</span><span class=\"p\">):</span>\n</span><span id=\"__span-6-3\"><a id=\"__codelineno-6-3\" name=\"__codelineno-6-3\" href=\"#__codelineno-6-3\"></a>    <span class=\"o\">...</span>\n</span><span id=\"__span-6-4\"><a id=\"__codelineno-6-4\" name=\"__codelineno-6-4\" href=\"#__codelineno-6-4\"></a>    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">__array__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">):</span>\n</span><span id=\"__span-6-5\"><a id=\"__codelineno-6-5\" name=\"__codelineno-6-5\" href=\"#__codelineno-6-5\"></a>        <span class=\"k\">return</span> <span class=\"n\">np</span><span class=\"o\">.</span><span class=\"n\">array</span><span class=\"p\">([</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">z</span><span class=\"p\">])</span>\n</span></code></pre></div>\n<p>That will definitely work, but it breaks the <a href=\"https://en.wikipedia.org/wiki/Don%27t_repeat_yourself\">DRY</a> principle.\nInstead, we can make use of <a href=\"https://docs.python.org/3/library/dataclasses.html#dataclasses.astuple\"><code>dataclasses.astuple</code></a></p>\n<div class=\"language-python highlight\"><pre><span></span><code><span id=\"__span-7-1\"><a id=\"__codelineno-7-1\" name=\"__codelineno-7-1\" href=\"#__codelineno-7-1\"></a><span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">dataclasses</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">astuple</span>\n</span><span id=\"__span-7-2\"><a id=\"__codelineno-7-2\" name=\"__codelineno-7-2\" href=\"#__codelineno-7-2\"></a>\n</span><span id=\"__span-7-3\"><a id=\"__codelineno-7-3\" name=\"__codelineno-7-3\" href=\"#__codelineno-7-3\"></a><span class=\"nd\">@dataclass</span>\n</span><span id=\"__span-7-4\"><a id=\"__codelineno-7-4\" name=\"__codelineno-7-4\" href=\"#__codelineno-7-4\"></a><span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">Point2D</span><span class=\"p\">:</span>\n</span><span id=\"__span-7-5\"><a id=\"__codelineno-7-5\" name=\"__codelineno-7-5\" href=\"#__codelineno-7-5\"></a>    <span class=\"n\">x</span><span class=\"p\">:</span> <span class=\"nb\">float</span>\n</span><span id=\"__span-7-6\"><a id=\"__codelineno-7-6\" name=\"__codelineno-7-6\" href=\"#__codelineno-7-6\"></a>    <span class=\"n\">y</span><span class=\"p\">:</span> <span class=\"nb\">float</span>\n</span><span id=\"__span-7-7\"><a id=\"__codelineno-7-7\" name=\"__codelineno-7-7\" href=\"#__codelineno-7-7\"></a>    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">__array__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">):</span>\n</span><span id=\"__span-7-8\"><a id=\"__codelineno-7-8\" name=\"__codelineno-7-8\" href=\"#__codelineno-7-8\"></a>        <span class=\"k\">return</span> <span class=\"n\">np</span><span class=\"o\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">astuple</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">))</span>\n</span><span id=\"__span-7-9\"><a id=\"__codelineno-7-9\" name=\"__codelineno-7-9\" href=\"#__codelineno-7-9\"></a>\n</span><span id=\"__span-7-10\"><a id=\"__codelineno-7-10\" name=\"__codelineno-7-10\" href=\"#__codelineno-7-10\"></a><span class=\"nd\">@dataclass</span>\n</span><span id=\"__span-7-11\"><a id=\"__codelineno-7-11\" name=\"__codelineno-7-11\" href=\"#__codelineno-7-11\"></a><span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">Point3D</span><span class=\"p\">(</span><span class=\"n\">Point2D</span><span class=\"p\">):</span>\n</span><span id=\"__span-7-12\"><a id=\"__codelineno-7-12\" name=\"__codelineno-7-12\" href=\"#__codelineno-7-12\"></a>    <span class=\"n\">z</span><span class=\"p\">:</span> <span class=\"nb\">float</span>\n</span><span id=\"__span-7-13\"><a id=\"__codelineno-7-13\" name=\"__codelineno-7-13\" href=\"#__codelineno-7-13\"></a>\n</span><span id=\"__span-7-14\"><a id=\"__codelineno-7-14\" name=\"__codelineno-7-14\" href=\"#__codelineno-7-14\"></a><span class=\"n\">p</span> <span class=\"o\">=</span> <span class=\"n\">Point3D</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"o\">=</span><span class=\"mf\">0.2</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"o\">=</span><span class=\"mf\">3.0</span><span class=\"p\">,</span> <span class=\"n\">z</span><span class=\"o\">=-</span><span class=\"mf\">1.0</span><span class=\"p\">)</span>\n</span><span id=\"__span-7-15\"><a id=\"__codelineno-7-15\" name=\"__codelineno-7-15\" href=\"#__codelineno-7-15\"></a><span class=\"n\">arr</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"o\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">p</span><span class=\"p\">)</span>\n</span><span id=\"__span-7-16\"><a id=\"__codelineno-7-16\" name=\"__codelineno-7-16\" href=\"#__codelineno-7-16\"></a><span class=\"nb\">print</span><span class=\"p\">(</span><span class=\"n\">arr</span><span class=\"p\">,</span> <span class=\"nb\">type</span><span class=\"p\">(</span><span class=\"n\">arr</span><span class=\"p\">),</span> <span class=\"n\">arr</span><span class=\"o\">.</span><span class=\"n\">dtype</span><span class=\"p\">)</span>\n</span><span id=\"__span-7-17\"><a id=\"__codelineno-7-17\" name=\"__codelineno-7-17\" href=\"#__codelineno-7-17\"></a><span class=\"c1\"># [ 0.2  3.  -1. ] &lt;class &#39;numpy.ndarray&#39;&gt; float64</span>\n</span></code></pre></div>\n<p>Less repetition and less chance of mistakes. Nice.</p>\n<p>Our next issue is when dealing with more than one instance of these custom classes at a time.</p>\n<h2 id=\"converting-lists-of-custom-dataclasses-with-nested-conversion\">Converting lists of custom dataclasses with nested conversion</h2>\n<p>If I have a few <code>Point</code>s, I might want a 2D np.array with all the values. The naive approach would be to do</p>\n<div class=\"language-python highlight\"><pre><span></span><code><span id=\"__span-8-1\"><a id=\"__codelineno-8-1\" name=\"__codelineno-8-1\" href=\"#__codelineno-8-1\"></a><span class=\"n\">p1</span> <span class=\"o\">=</span> <span class=\"n\">Point3D</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">2</span><span class=\"p\">,</span> <span class=\"mi\">3</span><span class=\"p\">)</span>\n</span><span id=\"__span-8-2\"><a id=\"__codelineno-8-2\" name=\"__codelineno-8-2\" href=\"#__codelineno-8-2\"></a><span class=\"n\">p2</span> <span class=\"o\">=</span> <span class=\"n\">Point3D</span><span class=\"p\">(</span><span class=\"mi\">4</span><span class=\"p\">,</span> <span class=\"mi\">5</span><span class=\"p\">,</span> <span class=\"mi\">6</span><span class=\"p\">)</span>\n</span><span id=\"__span-8-3\"><a id=\"__codelineno-8-3\" name=\"__codelineno-8-3\" href=\"#__codelineno-8-3\"></a><span class=\"n\">list_of_points</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">p1</span><span class=\"p\">,</span> <span class=\"n\">p2</span><span class=\"p\">]</span> \n</span><span id=\"__span-8-4\"><a id=\"__codelineno-8-4\" name=\"__codelineno-8-4\" href=\"#__codelineno-8-4\"></a><span class=\"n\">arr</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"o\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">list_of_points</span><span class=\"p\">)</span>\n</span><span id=\"__span-8-5\"><a id=\"__codelineno-8-5\" name=\"__codelineno-8-5\" href=\"#__codelineno-8-5\"></a><span class=\"nb\">print</span><span class=\"p\">(</span><span class=\"n\">arr</span><span class=\"p\">,</span> <span class=\"nb\">type</span><span class=\"p\">(</span><span class=\"n\">arr</span><span class=\"p\">),</span> <span class=\"n\">arr</span><span class=\"o\">.</span><span class=\"n\">dtype</span><span class=\"p\">,</span> <span class=\"n\">arr</span><span class=\"o\">.</span><span class=\"n\">shape</span><span class=\"p\">)</span>\n</span><span id=\"__span-8-6\"><a id=\"__codelineno-8-6\" name=\"__codelineno-8-6\" href=\"#__codelineno-8-6\"></a><span class=\"c1\"># [Point3D(x=1, y=2, z=3) Point3D(x=4, y=5, z=6)] &lt;class &#39;numpy.ndarray&#39;&gt; object (2,)</span>\n</span></code></pre></div>\n<p>Not only do I not get what I expected, I even get a bunch of warnings from numpy that this is a no-go</p>\n<div class=\"language-text highlight\"><pre><span></span><code><span id=\"__span-9-1\"><a id=\"__codelineno-9-1\" name=\"__codelineno-9-1\" href=\"#__codelineno-9-1\"></a>&lt;input&gt;:3: FutureWarning: The input object of type &#39;Point3D&#39; is an array-like implementing one of the corresponding protocols (`__array__`, `__array_interface__` or `__array_struct__`); but not a sequence (or 0-D). In the future, this object will be coerced as if it was first converted using `np.array(obj)`. To retain the old behaviour, you have to either modify the type &#39;Point3D&#39;, or assign to an empty array created with `np.empty(correct_shape, dtype=object)`.\n</span><span id=\"__span-9-2\"><a id=\"__codelineno-9-2\" name=\"__codelineno-9-2\" href=\"#__codelineno-9-2\"></a>&lt;input&gt;:3: VisibleDeprecationWarning: Creating an ndarray from ragged nested sequences (which is a list-or-tuple of lists-or-tuples-or ndarrays with different lengths or shapes) is deprecated. If you meant to do this, you must specify &#39;dtype=object&#39; when creating the ndarray.\n</span></code></pre></div>\n<p>We already know we can get a numpy array from a single instance, so we can get around this hurdle with a simple list comprehension</p>\n<div class=\"language-python highlight\"><pre><span></span><code><span id=\"__span-10-1\"><a id=\"__codelineno-10-1\" name=\"__codelineno-10-1\" href=\"#__codelineno-10-1\"></a><span class=\"n\">arr</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"o\">.</span><span class=\"n\">array</span><span class=\"p\">([</span><span class=\"n\">np</span><span class=\"o\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">p</span><span class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">p</span> <span class=\"ow\">in</span> <span class=\"n\">list_of_points</span><span class=\"p\">])</span>\n</span><span id=\"__span-10-2\"><a id=\"__codelineno-10-2\" name=\"__codelineno-10-2\" href=\"#__codelineno-10-2\"></a><span class=\"nb\">print</span><span class=\"p\">(</span><span class=\"n\">arr</span><span class=\"p\">,</span> <span class=\"nb\">type</span><span class=\"p\">(</span><span class=\"n\">arr</span><span class=\"p\">),</span> <span class=\"n\">arr</span><span class=\"o\">.</span><span class=\"n\">dtype</span><span class=\"p\">,</span> <span class=\"n\">arr</span><span class=\"o\">.</span><span class=\"n\">shape</span><span class=\"p\">)</span>\n</span><span id=\"__span-10-3\"><a id=\"__codelineno-10-3\" name=\"__codelineno-10-3\" href=\"#__codelineno-10-3\"></a><span class=\"c1\"># [[1 2 3]</span>\n</span><span id=\"__span-10-4\"><a id=\"__codelineno-10-4\" name=\"__codelineno-10-4\" href=\"#__codelineno-10-4\"></a><span class=\"c1\"># [4 5 6]] &lt;class &#39;numpy.ndarray&#39;&gt; int32 (2, 3)</span>\n</span></code></pre></div>\n<p>That works, but it feels more like a workaround than a real solution. Should I really have to remember to do this nested conversion every time I want to get my data in a 2D matrix?</p>\n<p>No, if I just implement two additional methods on the base class, I don't have to think about this any more.</p>\n<h2 id=\"converting-lists-of-custom-dataclasses-with-__len__-and-__getitem__\"><a id=\"the-real-solution\"></a> Converting lists of custom dataclasses with <code>__len__</code> and <code>__getitem__</code></h2>\n<div class=\"language-python highlight\"><pre><span></span><code><span id=\"__span-11-1\"><a id=\"__codelineno-11-1\" name=\"__codelineno-11-1\" href=\"#__codelineno-11-1\"></a><span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">dataclasses</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">dataclass</span><span class=\"p\">,</span> <span class=\"n\">astuple</span>\n</span><span id=\"__span-11-2\"><a id=\"__codelineno-11-2\" name=\"__codelineno-11-2\" href=\"#__codelineno-11-2\"></a><span class=\"kn\">import</span><span class=\"w\"> </span><span class=\"nn\">numpy</span><span class=\"w\"> </span><span class=\"k\">as</span><span class=\"w\"> </span><span class=\"nn\">np</span>\n</span><span id=\"__span-11-3\"><a id=\"__codelineno-11-3\" name=\"__codelineno-11-3\" href=\"#__codelineno-11-3\"></a>\n</span><span id=\"__span-11-4\"><a id=\"__codelineno-11-4\" name=\"__codelineno-11-4\" href=\"#__codelineno-11-4\"></a><span class=\"nd\">@dataclass</span>\n</span><span id=\"__span-11-5\"><a id=\"__codelineno-11-5\" name=\"__codelineno-11-5\" href=\"#__codelineno-11-5\"></a><span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">Point2D</span><span class=\"p\">:</span>\n</span><span id=\"__span-11-6\"><a id=\"__codelineno-11-6\" name=\"__codelineno-11-6\" href=\"#__codelineno-11-6\"></a>    <span class=\"n\">x</span><span class=\"p\">:</span> <span class=\"nb\">float</span>\n</span><span id=\"__span-11-7\"><a id=\"__codelineno-11-7\" name=\"__codelineno-11-7\" href=\"#__codelineno-11-7\"></a>    <span class=\"n\">y</span><span class=\"p\">:</span> <span class=\"nb\">float</span>\n</span><span id=\"__span-11-8\"><a id=\"__codelineno-11-8\" name=\"__codelineno-11-8\" href=\"#__codelineno-11-8\"></a>\n</span><span id=\"__span-11-9\"><a id=\"__codelineno-11-9\" name=\"__codelineno-11-9\" href=\"#__codelineno-11-9\"></a>    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">__array__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">):</span>\n</span><span id=\"__span-11-10\"><a id=\"__codelineno-11-10\" name=\"__codelineno-11-10\" href=\"#__codelineno-11-10\"></a>        <span class=\"k\">return</span> <span class=\"n\">np</span><span class=\"o\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">astuple</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">))</span>\n</span><span id=\"__span-11-11\"><a id=\"__codelineno-11-11\" name=\"__codelineno-11-11\" href=\"#__codelineno-11-11\"></a>\n</span><span id=\"__span-11-12\"><a id=\"__codelineno-11-12\" name=\"__codelineno-11-12\" href=\"#__codelineno-11-12\"></a>    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"fm\">__len__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">):</span>\n</span><span id=\"__span-11-13\"><a id=\"__codelineno-11-13\" name=\"__codelineno-11-13\" href=\"#__codelineno-11-13\"></a>        <span class=\"k\">return</span> <span class=\"n\">astuple</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">)</span><span class=\"o\">.</span><span class=\"fm\">__len__</span><span class=\"p\">()</span>\n</span><span id=\"__span-11-14\"><a id=\"__codelineno-11-14\" name=\"__codelineno-11-14\" href=\"#__codelineno-11-14\"></a>\n</span><span id=\"__span-11-15\"><a id=\"__codelineno-11-15\" name=\"__codelineno-11-15\" href=\"#__codelineno-11-15\"></a>    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"fm\">__getitem__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">item</span><span class=\"p\">):</span>\n</span><span id=\"__span-11-16\"><a id=\"__codelineno-11-16\" name=\"__codelineno-11-16\" href=\"#__codelineno-11-16\"></a>        <span class=\"k\">return</span> <span class=\"n\">astuple</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">)</span><span class=\"o\">.</span><span class=\"fm\">__getitem__</span><span class=\"p\">(</span><span class=\"n\">item</span><span class=\"p\">)</span>\n</span><span id=\"__span-11-17\"><a id=\"__codelineno-11-17\" name=\"__codelineno-11-17\" href=\"#__codelineno-11-17\"></a>\n</span><span id=\"__span-11-18\"><a id=\"__codelineno-11-18\" name=\"__codelineno-11-18\" href=\"#__codelineno-11-18\"></a><span class=\"nd\">@dataclass</span>\n</span><span id=\"__span-11-19\"><a id=\"__codelineno-11-19\" name=\"__codelineno-11-19\" href=\"#__codelineno-11-19\"></a><span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">Point3D</span><span class=\"p\">(</span><span class=\"n\">Point2D</span><span class=\"p\">):</span>\n</span><span id=\"__span-11-20\"><a id=\"__codelineno-11-20\" name=\"__codelineno-11-20\" href=\"#__codelineno-11-20\"></a>    <span class=\"n\">z</span><span class=\"p\">:</span> <span class=\"nb\">float</span>\n</span><span id=\"__span-11-21\"><a id=\"__codelineno-11-21\" name=\"__codelineno-11-21\" href=\"#__codelineno-11-21\"></a>\n</span><span id=\"__span-11-22\"><a id=\"__codelineno-11-22\" name=\"__codelineno-11-22\" href=\"#__codelineno-11-22\"></a><span class=\"n\">p1</span> <span class=\"o\">=</span> <span class=\"n\">Point3D</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"mi\">2</span><span class=\"p\">,</span> <span class=\"mi\">3</span><span class=\"p\">)</span>\n</span><span id=\"__span-11-23\"><a id=\"__codelineno-11-23\" name=\"__codelineno-11-23\" href=\"#__codelineno-11-23\"></a><span class=\"n\">p2</span> <span class=\"o\">=</span> <span class=\"n\">Point3D</span><span class=\"p\">(</span><span class=\"mi\">4</span><span class=\"p\">,</span> <span class=\"mi\">5</span><span class=\"p\">,</span> <span class=\"mi\">6</span><span class=\"p\">)</span>\n</span><span id=\"__span-11-24\"><a id=\"__codelineno-11-24\" name=\"__codelineno-11-24\" href=\"#__codelineno-11-24\"></a><span class=\"n\">list_of_points</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span class=\"n\">p1</span><span class=\"p\">,</span> <span class=\"n\">p2</span><span class=\"p\">]</span> \n</span><span id=\"__span-11-25\"><a id=\"__codelineno-11-25\" name=\"__codelineno-11-25\" href=\"#__codelineno-11-25\"></a><span class=\"n\">arr</span> <span class=\"o\">=</span> <span class=\"n\">np</span><span class=\"o\">.</span><span class=\"n\">array</span><span class=\"p\">(</span><span class=\"n\">list_of_points</span><span class=\"p\">)</span>\n</span><span id=\"__span-11-26\"><a id=\"__codelineno-11-26\" name=\"__codelineno-11-26\" href=\"#__codelineno-11-26\"></a><span class=\"nb\">print</span><span class=\"p\">(</span><span class=\"n\">arr</span><span class=\"p\">,</span> <span class=\"nb\">type</span><span class=\"p\">(</span><span class=\"n\">arr</span><span class=\"p\">),</span> <span class=\"n\">arr</span><span class=\"o\">.</span><span class=\"n\">dtype</span><span class=\"p\">,</span> <span class=\"n\">arr</span><span class=\"o\">.</span><span class=\"n\">shape</span><span class=\"p\">)</span>\n</span><span id=\"__span-11-27\"><a id=\"__codelineno-11-27\" name=\"__codelineno-11-27\" href=\"#__codelineno-11-27\"></a><span class=\"c1\"># [[1 2 3]</span>\n</span><span id=\"__span-11-28\"><a id=\"__codelineno-11-28\" name=\"__codelineno-11-28\" href=\"#__codelineno-11-28\"></a><span class=\"c1\"># [4 5 6]] &lt;class &#39;numpy.ndarray&#39;&gt; int32 (2, 3)</span>\n</span></code></pre></div>\n<p>We are again abusing <code>dataclass.astuple</code> to let us access each class variable programatically, in order.</p>\n<p>To be honest, I don't really understand why <code>__array__</code> does not work for lists of custom dataclasses,\nbut <code>__len__</code> and <code>__getitem__</code> does.\nIf numpy is looping through each element one at a time to add it to an array,\nwe might run into some performance issues at some point.</p>\n<p>But, for now, this looks fairly clean for my taste and it is very practical.</p>", "image": null, "date_published": "2021-08-11T00:00:00+00:00", "authors": [], "tags": null}, {"id": "https://kplauritzen.dk/2019-02-27-thistextdoesnotexist/", "url": "https://kplauritzen.dk/2019-02-27-thistextdoesnotexist/", "title": "This text does not exist", "content_html": "<h1 id=\"this-text-does-not-exist\">This text does not exist</h1>\n<p><code>TL;DR</code>: I stole a thing.</p>\n<h2 id=\"generating-text-for-fun-and-profit\">Generating text for fun and profit</h2>\n<p>I stole samples from the recent OpenAI <a href=\"https://blog.openai.com/better-language-models/\">language model</a>, generating text without human input. This continues the <a href=\"http://www.thisairbnbdoesnotexist.com/\">excellent</a> <a href=\"https://www.thiswaifudoesnotexist.net/\">work</a> <a href=\"https://www.thispersondoesnotexist.com/\">of</a> <a href=\"https://thiscatdoesnotexist.com/\">many</a> <a href=\"https://github.com/paubric/awesome-doesnotexist\">others</a>.</p>\n<p>Many thanks to <a href=\"https://github.com/pgericson\">Peter Ericson</a> for helping me bodge some javascript together for this project. I have absolutely no idea what I'm doing, so anything that remotely works is entirely Peters fault.</p>\n<p>See the thing <a href=\"http://kplauritzen.dk/thistextdoesnotexist/\">here</a> and we also bought <a href=\"http://thistextdoesnotexist.com\">thistextdoesnotexist.com</a> because that seemed reasonable at the time.</p>", "image": null, "date_published": "2019-02-27T00:00:00+00:00", "authors": [], "tags": null}, {"id": "https://kplauritzen.dk/2018-06-13-worldcup/", "url": "https://kplauritzen.dk/2018-06-13-worldcup/", "title": "Predicting the 2018 FIFA World Cup", "content_html": "<h1 id=\"predicting-the-2018-fifa-world-cup\">Predicting the 2018 FIFA World Cup</h1>\n<p><code>TL;DR</code>: Check the predictions at the bottom of the page, and see the code on <a href=\"https://github.com/KPLauritzen/worldcup2018\">GitHub</a>.</p>\n<p>The FIFA World Cup 2018 is starting tomorrow in Russia.\nI joined a competition at work to predict the final score of all the games of the tournament, but since I don't know anything about football (and do know <em>something</em> about machine learning), I thought it would be fun to build a simple model to do the predictions.</p>\n<h2 id=\"the-challenge\">The challenge</h2>\n<p>I want to guess the final score of all matches. There is partial credit for just guessing the match winner, so if it is not possible to guess the final score I want to err towards guessing the winner.</p>\n<p>I see two main challenges:</p>\n<ol>\n<li>\n<p>I have to predict how the national teams will perform. Each team usually plays less than 10 games a year, so there is not a lot of data to go on.</p>\n</li>\n<li>\n<p>I have to predict scores instead of just outcomes. So this is not a classification problem, but instead a regression problem. I'm not so confident in that class of machine learning problems, so this will be a fun exercise.</p>\n</li>\n</ol>\n<h2 id=\"initial-thoughts\">Initial thoughts</h2>\n<p>I will be using the team ratings from the game FIFA 18 as my training features. They are available for both international and club games and they are updated every week.\nFor each team there are ratings for attacking ('ATT'), midfield ('MID'), defending ('DEF') and overall ('OVR').\nMY hypothesis is that if I find a pattern in how many goals a 85 ATT teams scores against a 74 DEF team, this will hold for both clubs and international teams. In this way I will be able to train on many games from for example premier league.</p>\n<p>Additionally, I will include the implied odds from bookmaker odds for each match outcome. I hope this will make it more likely to predict the correct winner as this also gives some points in my office tournament.</p>\n<h2 id=\"data-sources\">Data Sources</h2>\n<p>I found FIFA team ratings at <a href=\"https://www.fifaindex.com/\">FIFA index</a>. This was not available as a download, so I had to scrape the ratings.</p>\n<p>The final scores of matches for several European leagues are available at <a href=\"http://www.football-data.co.uk/\">Football-data.co.uk</a>. Here the data is available as a <code>.csv</code> file, so this couldn't be easier. It even includes bookmaker odds!</p>\n<p>The odds from the world cup games was downloaded and scraped from <a href=\"https://www.oddschecker.com\">Odds Checker</a>.</p>\n<p>The fixtures for the world cup was downloaded from <a href=\"https://fixturedownload.com\">Fixture Download</a>.</p>\n<h2 id=\"my-approach\">My approach</h2>\n<p>I wanted to use this as an opportunity to use <a href=\"https://luigi.readthedocs.io/en/stable/\">luigi</a> for handling the data dependencies. This is definitely overkill for a simple project like this, but it was still a good testing grounds.\nTo illustrate, here is a portion of the final dependency graph for creating the training dataset.</p>\n<p><img alt=\"luigi\" src=\"../images/luigi.png\" /></p>\n<p>You can check out the code at <a href=\"https://github.com/KPLauritzen/worldcup2018\">my GitHub</a>.</p>\n<p>To summarize the training data creation:</p>\n<ol>\n<li>\n<p>For a certain league (e.g. Premier League) I download the weekly team ratings pages from FIFA Index, using <code>urllib</code>.</p>\n</li>\n<li>\n<p>I extract the ratings for each team with <code>beautifulsoup4</code>.</p>\n</li>\n<li>\n<p>I download the results of all games in the league from football-data. This dataset includes bookmaker odds. I use the Bet365 odds.</p>\n</li>\n<li>\n<p>I merge the ratings for home and away teams onto the results, using <code>pandas</code>. I merge on team name and date.</p>\n<ul>\n<li>NB: The team names are not consistent between the two data sources, so I had to normalize the names to be able to join on them.</li>\n</ul>\n</li>\n<li>\n<p>To avoid having to account for home field advantage (which won't be a thing at the world cup - except for Russia), I add all matches AGAIN with home and away team reversed. This should make home and away teams symmetric.</p>\n</li>\n<li>\n<p>Repeat steps 1-5 for other leagues if desired. It could be beneficial to include lower tier leagues to have examples of ratings like the lower tier national teams.</p>\n</li>\n</ol>\n<h2 id=\"training\">Training</h2>\n<p>I've not really explored the possibilities very carefully here. I've trained a Ridge regression in <code>scikit-learn</code> with home AND away goals as the targets.\nIt works the best of all the methods I tried with a mean absolute error of about 0.9. So on average my predictions are about 1 goal off the correct answer.</p>\n<h2 id=\"prediction-data\">Prediction data</h2>\n<p>The data gathering setup is very similar to the process described above for club games.</p>\n<p>One issue is that the predictions are not integers but floats. How do you interpret a prediction of 1.43 against 1.18? Should that be rounded to 1-1? How about 0.6 against 1.4? Is it still a 1-1 if I predict one team to have 0.8 goals advantage?</p>\n<p>I think there is a smart answer to this, maybe using the bookmaker odds to decide if this should be a draw or if one team winning is more likely.</p>\n<p>I haven't figured this out, and I would love some more input. Please reach out if you have an idea.</p>\n<h2 id=\"actual-predictions\">Actual predictions</h2>\n<p>No more beating around the bush. Here are my final predictions:</p>\n<div class=\"language-text highlight\"><pre><span></span><code><span id=\"__span-0-1\"><a id=\"__codelineno-0-1\" name=\"__codelineno-0-1\" href=\"#__codelineno-0-1\"></a>| team_home    | goals_home | goals_away | team_away    |\n</span><span id=\"__span-0-2\"><a id=\"__codelineno-0-2\" name=\"__codelineno-0-2\" href=\"#__codelineno-0-2\"></a>|--------------|------------|------------|--------------|\n</span><span id=\"__span-0-3\"><a id=\"__codelineno-0-3\" name=\"__codelineno-0-3\" href=\"#__codelineno-0-3\"></a>| Russia       |          2 |          1 | Saudi Arabia |\n</span><span id=\"__span-0-4\"><a id=\"__codelineno-0-4\" name=\"__codelineno-0-4\" href=\"#__codelineno-0-4\"></a>| Egypt        |          0 |          2 | Uruguay      |\n</span><span id=\"__span-0-5\"><a id=\"__codelineno-0-5\" name=\"__codelineno-0-5\" href=\"#__codelineno-0-5\"></a>| Morocco      |          1 |          1 | Iran         |\n</span><span id=\"__span-0-6\"><a id=\"__codelineno-0-6\" name=\"__codelineno-0-6\" href=\"#__codelineno-0-6\"></a>| Portugal     |          1 |          2 | Spain        |\n</span><span id=\"__span-0-7\"><a id=\"__codelineno-0-7\" name=\"__codelineno-0-7\" href=\"#__codelineno-0-7\"></a>| France       |          2 |          1 | Australia    |\n</span><span id=\"__span-0-8\"><a id=\"__codelineno-0-8\" name=\"__codelineno-0-8\" href=\"#__codelineno-0-8\"></a>| Argentina    |          2 |          1 | Iceland      |\n</span><span id=\"__span-0-9\"><a id=\"__codelineno-0-9\" name=\"__codelineno-0-9\" href=\"#__codelineno-0-9\"></a>| Peru         |          1 |          1 | Denmark      |\n</span><span id=\"__span-0-10\"><a id=\"__codelineno-0-10\" name=\"__codelineno-0-10\" href=\"#__codelineno-0-10\"></a>| Croatia      |          2 |          1 | Nigeria      |\n</span><span id=\"__span-0-11\"><a id=\"__codelineno-0-11\" name=\"__codelineno-0-11\" href=\"#__codelineno-0-11\"></a>| Costa Rica   |          1 |          1 | Serbia       |\n</span><span id=\"__span-0-12\"><a id=\"__codelineno-0-12\" name=\"__codelineno-0-12\" href=\"#__codelineno-0-12\"></a>| Germany      |          2 |          1 | Mexico       |\n</span><span id=\"__span-0-13\"><a id=\"__codelineno-0-13\" name=\"__codelineno-0-13\" href=\"#__codelineno-0-13\"></a>| Brazil       |          2 |          1 | Switzerland  |\n</span><span id=\"__span-0-14\"><a id=\"__codelineno-0-14\" name=\"__codelineno-0-14\" href=\"#__codelineno-0-14\"></a>| Sweden       |          1 |          1 | South Korea  |\n</span><span id=\"__span-0-15\"><a id=\"__codelineno-0-15\" name=\"__codelineno-0-15\" href=\"#__codelineno-0-15\"></a>| Belgium      |          2 |          1 | Panama       |\n</span><span id=\"__span-0-16\"><a id=\"__codelineno-0-16\" name=\"__codelineno-0-16\" href=\"#__codelineno-0-16\"></a>| Tunisia      |          1 |          2 | England      |\n</span><span id=\"__span-0-17\"><a id=\"__codelineno-0-17\" name=\"__codelineno-0-17\" href=\"#__codelineno-0-17\"></a>| Colombia     |          2 |          1 | Japan        |\n</span><span id=\"__span-0-18\"><a id=\"__codelineno-0-18\" name=\"__codelineno-0-18\" href=\"#__codelineno-0-18\"></a>| Poland       |          1 |          1 | Senegal      |\n</span><span id=\"__span-0-19\"><a id=\"__codelineno-0-19\" name=\"__codelineno-0-19\" href=\"#__codelineno-0-19\"></a>| Russia       |          1 |          1 | Egypt        |\n</span><span id=\"__span-0-20\"><a id=\"__codelineno-0-20\" name=\"__codelineno-0-20\" href=\"#__codelineno-0-20\"></a>| Portugal     |          2 |          1 | Morocco      |\n</span><span id=\"__span-0-21\"><a id=\"__codelineno-0-21\" name=\"__codelineno-0-21\" href=\"#__codelineno-0-21\"></a>| Uruguay      |          2 |          0 | Saudi Arabia |\n</span><span id=\"__span-0-22\"><a id=\"__codelineno-0-22\" name=\"__codelineno-0-22\" href=\"#__codelineno-0-22\"></a>| Iran         |          1 |          2 | Spain        |\n</span><span id=\"__span-0-23\"><a id=\"__codelineno-0-23\" name=\"__codelineno-0-23\" href=\"#__codelineno-0-23\"></a>| Denmark      |          2 |          1 | Australia    |\n</span><span id=\"__span-0-24\"><a id=\"__codelineno-0-24\" name=\"__codelineno-0-24\" href=\"#__codelineno-0-24\"></a>| France       |          2 |          1 | Peru         |\n</span><span id=\"__span-0-25\"><a id=\"__codelineno-0-25\" name=\"__codelineno-0-25\" href=\"#__codelineno-0-25\"></a>| Argentina    |          2 |          1 | Croatia      |\n</span><span id=\"__span-0-26\"><a id=\"__codelineno-0-26\" name=\"__codelineno-0-26\" href=\"#__codelineno-0-26\"></a>| Brazil       |          2 |          0 | Costa Rica   |\n</span><span id=\"__span-0-27\"><a id=\"__codelineno-0-27\" name=\"__codelineno-0-27\" href=\"#__codelineno-0-27\"></a>| Nigeria      |          1 |          1 | Iceland      |\n</span><span id=\"__span-0-28\"><a id=\"__codelineno-0-28\" name=\"__codelineno-0-28\" href=\"#__codelineno-0-28\"></a>| Serbia       |          1 |          1 | Switzerland  |\n</span><span id=\"__span-0-29\"><a id=\"__codelineno-0-29\" name=\"__codelineno-0-29\" href=\"#__codelineno-0-29\"></a>| Belgium      |          2 |          1 | Tunisia      |\n</span><span id=\"__span-0-30\"><a id=\"__codelineno-0-30\" name=\"__codelineno-0-30\" href=\"#__codelineno-0-30\"></a>| South Korea  |          1 |          2 | Mexico       |\n</span><span id=\"__span-0-31\"><a id=\"__codelineno-0-31\" name=\"__codelineno-0-31\" href=\"#__codelineno-0-31\"></a>| Germany      |          2 |          1 | Sweden       |\n</span><span id=\"__span-0-32\"><a id=\"__codelineno-0-32\" name=\"__codelineno-0-32\" href=\"#__codelineno-0-32\"></a>| England      |          2 |          1 | Panama       |\n</span><span id=\"__span-0-33\"><a id=\"__codelineno-0-33\" name=\"__codelineno-0-33\" href=\"#__codelineno-0-33\"></a>| Japan        |          1 |          1 | Senegal      |\n</span><span id=\"__span-0-34\"><a id=\"__codelineno-0-34\" name=\"__codelineno-0-34\" href=\"#__codelineno-0-34\"></a>| Poland       |          1 |          1 | Colombia     |\n</span><span id=\"__span-0-35\"><a id=\"__codelineno-0-35\" name=\"__codelineno-0-35\" href=\"#__codelineno-0-35\"></a>| Uruguay      |          1 |          1 | Russia       |\n</span><span id=\"__span-0-36\"><a id=\"__codelineno-0-36\" name=\"__codelineno-0-36\" href=\"#__codelineno-0-36\"></a>| Saudi Arabia |          1 |          2 | Egypt        |\n</span><span id=\"__span-0-37\"><a id=\"__codelineno-0-37\" name=\"__codelineno-0-37\" href=\"#__codelineno-0-37\"></a>| Iran         |          1 |          2 | Portugal     |\n</span><span id=\"__span-0-38\"><a id=\"__codelineno-0-38\" name=\"__codelineno-0-38\" href=\"#__codelineno-0-38\"></a>| Spain        |          2 |          1 | Morocco      |\n</span><span id=\"__span-0-39\"><a id=\"__codelineno-0-39\" name=\"__codelineno-0-39\" href=\"#__codelineno-0-39\"></a>| Denmark      |          1 |          2 | France       |\n</span><span id=\"__span-0-40\"><a id=\"__codelineno-0-40\" name=\"__codelineno-0-40\" href=\"#__codelineno-0-40\"></a>| Australia    |          1 |          1 | Peru         |\n</span><span id=\"__span-0-41\"><a id=\"__codelineno-0-41\" name=\"__codelineno-0-41\" href=\"#__codelineno-0-41\"></a>| Nigeria      |          1 |          2 | Argentina    |\n</span><span id=\"__span-0-42\"><a id=\"__codelineno-0-42\" name=\"__codelineno-0-42\" href=\"#__codelineno-0-42\"></a>| Iceland      |          1 |          2 | Croatia      |\n</span><span id=\"__span-0-43\"><a id=\"__codelineno-0-43\" name=\"__codelineno-0-43\" href=\"#__codelineno-0-43\"></a>| Mexico       |          1 |          1 | Sweden       |\n</span><span id=\"__span-0-44\"><a id=\"__codelineno-0-44\" name=\"__codelineno-0-44\" href=\"#__codelineno-0-44\"></a>| South Korea  |          1 |          2 | Germany      |\n</span><span id=\"__span-0-45\"><a id=\"__codelineno-0-45\" name=\"__codelineno-0-45\" href=\"#__codelineno-0-45\"></a>| Serbia       |          1 |          2 | Brazil       |\n</span><span id=\"__span-0-46\"><a id=\"__codelineno-0-46\" name=\"__codelineno-0-46\" href=\"#__codelineno-0-46\"></a>| Switzerland  |          1 |          1 | Costa Rica   |\n</span><span id=\"__span-0-47\"><a id=\"__codelineno-0-47\" name=\"__codelineno-0-47\" href=\"#__codelineno-0-47\"></a>| Japan        |          1 |          1 | Poland       |\n</span><span id=\"__span-0-48\"><a id=\"__codelineno-0-48\" name=\"__codelineno-0-48\" href=\"#__codelineno-0-48\"></a>| Senegal      |          1 |          1 | Colombia     |\n</span><span id=\"__span-0-49\"><a id=\"__codelineno-0-49\" name=\"__codelineno-0-49\" href=\"#__codelineno-0-49\"></a>| Panama       |          1 |          1 | Tunisia      |\n</span><span id=\"__span-0-50\"><a id=\"__codelineno-0-50\" name=\"__codelineno-0-50\" href=\"#__codelineno-0-50\"></a>| England      |          1 |          1 | Belgium      |\n</span></code></pre></div>\n<h2 id=\"related-work\">Related work</h2>\n<p>There is a <a href=\"https://arxiv.org/pdf/1806.03208.pdf\">very nice paper</a> by Andreas Groll <em>et al.</em>, doing the same thing but better and with much more rigor.</p>\n<p>And this great tweet in response to the paper:</p>\n<blockquote>\n<p>Your first mistake was using fifa rankings...they are an absolute horrible indicator of a countrys actual ability</p>\n</blockquote>\n<p><a href=\"https://twitter.com/markobilal/status/1006666154503540737?ref_src=twsrc%5Etfw\">Marko Bilal (@markobilal)</a> on Twitter</p>\n<p>Of course, the ever interesting <a href=\"http://www.fivethirtyeight.com\">FiveThirtyEight</a> have also tried their hand at <a href=\"https://projects.fivethirtyeight.com/2018-world-cup-predictions/\">predicting the world cup</a>. They also publish <a href=\"https://fivethirtyeight.com/features/how-our-2018-world-cup-predictions-work/\">their methodology</a> and <a href=\"https://github.com/fivethirtyeight/data/tree/master/world-cup-2018\">their data</a>. How can you not love them?</p>", "image": null, "date_published": "2018-06-13T00:00:00+00:00", "authors": [], "tags": null}]}