<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Automation on Backend Engineering Strategy Tools</title><link>https://backend-engineering-strategy-tools.github.io/site/tags/automation/</link><description>Recent content in Automation on Backend Engineering Strategy Tools</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Wed, 03 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://backend-engineering-strategy-tools.github.io/site/tags/automation/index.xml" rel="self" type="application/rss+xml"/><item><title>Make — Task Runner Pattern</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/cicd/make/</link><pubDate>Wed, 03 Jun 2026 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/cicd/make/</guid><description>&lt;p&gt;Make predates most of the tooling in this notes collection by decades. Originally built to manage C compilation — track which source files changed, recompile only what&amp;rsquo;s needed. The dependency graph and incremental execution model are genuinely elegant.&lt;/p&gt;
&lt;p&gt;In modern use, Make is rarely used for what it was designed for. It is used as a &lt;strong&gt;task runner&lt;/strong&gt;: a consistent interface that wraps whatever the project actually needs to do.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-makefile" data-lang="makefile"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;.PHONY&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; build test lint deploy
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;build&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	docker build -t myapp .
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;test&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	docker run --rm myapp pytest
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;lint&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	docker run --rm myapp ruff check .
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;deploy&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	helm upgrade --install myapp ./chart
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;make build&lt;/code&gt;, &lt;code&gt;make test&lt;/code&gt;, &lt;code&gt;make lint&lt;/code&gt; — same interface regardless of what&amp;rsquo;s underneath. New team member, CI pipeline, colleague&amp;rsquo;s machine: one place to look.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="why-it-still-works"&gt;Why it still works
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Installed everywhere. No setup, no version to pin.&lt;/li&gt;
&lt;li&gt;Self-documenting entry point — &lt;code&gt;cat Makefile&lt;/code&gt; tells you how to work with the project.&lt;/li&gt;
&lt;li&gt;Trivially wraps Docker, shell scripts, language-specific tools, cloud CLIs.&lt;/li&gt;
&lt;li&gt;Low overhead — it is just shell execution with a thin layer on top.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The fact that it is old is not a weakness. It is stable, understood, and available.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="the-phony-problem"&gt;The &lt;code&gt;.PHONY&lt;/code&gt; problem
&lt;/h2&gt;&lt;p&gt;Make&amp;rsquo;s native model assumes targets are files. &lt;code&gt;make build&lt;/code&gt; checks if a file named &lt;code&gt;build&lt;/code&gt; exists and skips the recipe if it does. &lt;code&gt;.PHONY&lt;/code&gt; declares that a target is not a file, forcing it to always run.&lt;/p&gt;
&lt;p&gt;Forgetting &lt;code&gt;.PHONY&lt;/code&gt; is a classic Make footgun. Always declare task targets phony.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-makefile" data-lang="makefile"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;.PHONY&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; build test lint clean
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="modern-alternatives"&gt;Modern alternatives
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Just&lt;/strong&gt; (&lt;code&gt;Justfile&lt;/code&gt;) — purpose-built task runner, not a build system. Cleaner syntax than Make, no file-dependency model to work around, better error messages. Cross-platform. Requires installation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Task&lt;/strong&gt; (&lt;code&gt;Taskfile.yml&lt;/code&gt;) — YAML-based, similar goals to Just. More readable for people uncomfortable with Make syntax.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why still use Make then&lt;/strong&gt;: zero install cost, ubiquity in CI environments, and for many projects the &lt;code&gt;.PHONY&lt;/code&gt; quirk is the only real friction. If the team already knows Make and the project isn&amp;rsquo;t complex, switching to Just adds a dependency without solving a real problem.&lt;/p&gt;
&lt;p&gt;If starting fresh on a project where the team is comfortable with the tooling choice, Just is the cleaner option.&lt;/p&gt;
&lt;p&gt;It is there, it works. That is not faint praise — ubiquity and stability are real value. Every project gets a Makefile. New person joins, CI pipeline runs, colleague needs to build something: &lt;code&gt;make&lt;/code&gt; is the answer and it requires no explanation.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="pattern-shared-interface-over-varied-internals"&gt;Pattern: shared interface over varied internals
&lt;/h2&gt;&lt;p&gt;The real value is not Make specifically — it is the pattern of having one consistent entry point regardless of what&amp;rsquo;s underneath. Whether that is Make, Just, or a &lt;code&gt;scripts/&lt;/code&gt; directory with a &lt;code&gt;run&lt;/code&gt; script, the goal is the same: anyone (person or pipeline) can run the project without knowing its internals.&lt;/p&gt;
&lt;p&gt;See also &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/thinking/shared-tooling-images/" &gt;Shared Tooling Images&lt;/a&gt; — the same principle applied to the tools themselves.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="where-this-pattern-applies"&gt;Where this pattern applies
&lt;/h2&gt;&lt;p&gt;The signal that logic is leaking into the wrong place: when a tool&amp;rsquo;s config format starts looking like a scripting language. That is the point to extract it into a script and wrap in Make.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Build systems&lt;/strong&gt;
Gradle compiles, tests, and packages. Deployment logic, Docker builds, release steps → scripts. &lt;code&gt;make build&lt;/code&gt; calls Gradle. &lt;code&gt;make docker&lt;/code&gt; calls a script. &lt;code&gt;make deploy&lt;/code&gt; calls Helm. See &lt;a class="link" href="https://backend-engineering-strategy-tools.github.io/site/public-notes/cicd/build-systems/" &gt;Build Systems&lt;/a&gt; for the full pattern.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Infrastructure&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Terraform&lt;/strong&gt;: &lt;code&gt;terraform apply&lt;/code&gt; does the infra. Environment selection, var file injection, state backend config, plan review → scripts. &lt;code&gt;make plan&lt;/code&gt;, &lt;code&gt;make apply&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Helm&lt;/strong&gt;: Helm packages and deploys. Cluster selection, secret fetching, values overrides → scripts. &lt;code&gt;make deploy&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ansible&lt;/strong&gt;: Ansible provisions. Inventory management, vault decryption, pre-flight checks → scripts. &lt;code&gt;make provision&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Testing&lt;/strong&gt;
Test runners run tests. Environment setup, test data seeding, coverage reporting → scripts. &lt;code&gt;make test&lt;/code&gt; is the entry point regardless of whether it is pytest, jest, or go test underneath.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Package managers&lt;/strong&gt;
A common npm/yarn trap: stuffing 20 scripts into &lt;code&gt;package.json&lt;/code&gt; until it becomes a second build system. Keep &lt;code&gt;package.json&lt;/code&gt; for dependency management. Orchestration belongs in Make.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Static site / Hugo&lt;/strong&gt;
Hugo builds the site. PDF fetching, Docker wrapping, deploy steps → Makefile. The pattern this site already uses.&lt;/p&gt;</description></item><item><title>Ansible</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/infra-as-code/ansible/</link><pubDate>Mon, 01 Jan 2024 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/infra-as-code/ansible/</guid><description>&lt;p&gt;Ansible is an open-source automation tool for configuration management, application deployment, and orchestration. The key selling point: it&amp;rsquo;s agentless — you push from a control machine over SSH, no daemon running on managed hosts.&lt;/p&gt;
&lt;h2 id="why-ansible"&gt;Why Ansible
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Free and open source&lt;/strong&gt; — Red Hat maintains it, commercially supported via Ansible Automation Platform (formerly Tower)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agentless&lt;/strong&gt; — no software to install on managed nodes; plain SSH is enough&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simple&lt;/strong&gt; — playbooks are YAML, readable without special knowledge&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Flexible&lt;/strong&gt; — works on servers, cloud platforms, network devices, and bare-metal&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-it-does"&gt;What it does
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Configuration management&lt;/strong&gt; — define what state a system should be in; Ansible gets it there and keeps it there.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Application deployment&lt;/strong&gt; — deploy multi-tier applications with a playbook; let Ansible figure out the ordering and state transitions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Orchestration&lt;/strong&gt; — coordinate complex workflows across databases, networks, front-end and back-end services in the right order.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Security and compliance&lt;/strong&gt; — enforce firewall rules, user policies, and security baselines across all hosts from a single playbook run.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cloud provisioning&lt;/strong&gt; — provision infrastructure on AWS, Azure, GCP, OpenStack, or bare-metal with the same tooling.&lt;/p&gt;
&lt;h2 id="architecture"&gt;Architecture
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Modules&lt;/strong&gt; — small programs pushed to nodes over SSH, executed, then removed. Ansible ships with 750+ modules for packages, services, files, cloud APIs, and more.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Plugins&lt;/strong&gt; — extend Ansible&amp;rsquo;s core: connection types, callbacks, caching, filtering. Write your own or use community plugins.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Inventory&lt;/strong&gt; — a file (INI or YAML) listing all managed hosts, their IPs, groups, and variables. Can also pull dynamic inventory from AWS, GCP, Azure, etc.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Playbooks&lt;/strong&gt; — YAML files describing tasks to run on which hosts. The core unit of work. Each play maps a group of hosts to a set of tasks; each task calls a module.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;APIs&lt;/strong&gt; — extend connection transports beyond SSH (WinRM for Windows, network device APIs, etc.).&lt;/p&gt;
&lt;h2 id="ansible-automation-platform"&gt;Ansible Automation Platform
&lt;/h2&gt;&lt;p&gt;Red Hat&amp;rsquo;s commercial wrapper around Ansible. Adds a web UI, RBAC, job scheduling, audit logging, and a workflow editor. Worth it once you have multiple teams running automation.&lt;/p&gt;
&lt;h2 id="where-ansible-still-excels"&gt;Where Ansible still excels
&lt;/h2&gt;&lt;p&gt;Network device configuration is the clearest remaining stronghold. Ansible has mature modules for switches, routers, and firewalls (Cisco IOS, Arista EOS, Juniper JunOS, and many others) and the agentless SSH model works well for network gear that can&amp;rsquo;t run an agent. For network automation, Ansible is still state of the art.&lt;/p&gt;
&lt;p&gt;For server configuration management — the original use case — most teams should consider moving to other models. Terraform manages provisioning, container images handle application configuration (immutable infrastructure), and Kubernetes operators handle runtime state. The gap Ansible used to fill is smaller.&lt;/p&gt;
&lt;h2 id="the-configuration-management-landscape"&gt;The configuration management landscape
&lt;/h2&gt;&lt;p&gt;Ansible is one of several tools in this space. The others worth knowing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Salt&lt;/strong&gt; (SaltStack) — agent-based, event-driven, fast for large fleets. More complex than Ansible.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Puppet&lt;/strong&gt; — agent-based, declarative DSL, strong in enterprise. Puppet Forge has a large module library.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Chef&lt;/strong&gt; — agent-based, Ruby DSL, infrastructure as code before the term was common. Less common now.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See &lt;a class="link" href="../config-management/" &gt;Configuration Management — Puppet, Chef, Salt&lt;/a&gt; for a full comparison.&lt;/p&gt;
&lt;p&gt;All four predate the Kubernetes era. Ansible has survived best because it is agentless and YAML-based — lower barrier. The others are still found in large enterprise environments with long-lived infrastructure.&lt;/p&gt;
&lt;h2 id="resources"&gt;Resources
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://docs.ansible.com/" target="_blank" rel="noopener"
 &gt;docs.ansible.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.ansible.com/" target="_blank" rel="noopener"
 &gt;ansible.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Bash</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/languages/bash/</link><pubDate>Mon, 01 Jan 2024 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/languages/bash/</guid><description>&lt;p&gt;Bash is unavoidable in DevOps work — CI/CD pipelines, container entrypoints, system init scripts, and quick automation all end up as shell scripts eventually. Knowing how to write it well saves a lot of pain.&lt;/p&gt;
&lt;h2 id="tooling"&gt;Tooling
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Linting:&lt;/strong&gt; &lt;a class="link" href="https://www.shellcheck.net/" target="_blank" rel="noopener"
 &gt;ShellCheck&lt;/a&gt;: catches common mistakes and anti-patterns. Run it in CI, or install the IDE plugin. Most of what it flags is genuinely wrong.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;IDE:&lt;/strong&gt; Any editor works. ShellCheck integrations exist for VS Code, IntelliJ, Vim, and most others.&lt;/p&gt;
&lt;h2 id="good-habits"&gt;Good habits
&lt;/h2&gt;&lt;p&gt;Set these at the top of every non-trivial script:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#!/usr/bin/env bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;set -euo pipefail
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-e&lt;/code&gt; — exit on error&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-u&lt;/code&gt; — error on unset variables&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-o pipefail&lt;/code&gt;: catch errors in pipes, not just the last command&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Use &lt;code&gt;shellcheck&lt;/code&gt; before committing. Most Bash bugs it catches are subtle and only surface under specific conditions in production.&lt;/p&gt;
&lt;h2 id="when-to-reach-for-python-instead"&gt;When to reach for Python instead
&lt;/h2&gt;&lt;p&gt;When the script grows beyond ~50 lines, needs associative arrays, JSON parsing, or HTTP calls — switch to Python. Bash is great glue; it&amp;rsquo;s a poor application language.&lt;/p&gt;
&lt;h2 id="resources"&gt;Resources
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.shellcheck.net/" target="_blank" rel="noopener"
 &gt;ShellCheck&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.gnu.org/software/bash/manual/" target="_blank" rel="noopener"
 &gt;Bash Reference Manual&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://google.github.io/styleguide/shellguide.html" target="_blank" rel="noopener"
 &gt;Google Shell Style Guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Python</title><link>https://backend-engineering-strategy-tools.github.io/site/public-notes/languages/python/</link><pubDate>Mon, 01 Jan 2024 00:00:00 +0000</pubDate><guid>https://backend-engineering-strategy-tools.github.io/site/public-notes/languages/python/</guid><description>&lt;p&gt;Python is my scripting and automation language of choice. I reach for it when Bash starts getting unwieldy — data processing, API interactions, infrastructure automation scripts, and one-off tooling.&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;2024 note:&lt;/strong&gt; For anything beyond quick scripts I&amp;rsquo;m increasingly reaching for Go instead. Better performance, a single compiled binary, and stronger typing make Go a more honest choice for tools that end up running in production. Python still wins for data work and anything where the ecosystem matters (ML, pandas, etc.).&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h2 id="tooling"&gt;Tooling
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;IDE:&lt;/strong&gt; &lt;a class="link" href="https://www.jetbrains.com/pycharm/" target="_blank" rel="noopener"
 &gt;PyCharm&lt;/a&gt;: JetBrains&amp;rsquo; Python IDE. Good for larger projects. For scripts and smaller work, IntelliJ IDEA with the Python plugin does the job.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Formatting:&lt;/strong&gt; &lt;a class="link" href="https://black.readthedocs.io/" target="_blank" rel="noopener"
 &gt;Black&lt;/a&gt;: opinionated formatter, no configuration needed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Linting:&lt;/strong&gt; &lt;a class="link" href="https://docs.astral.sh/ruff/" target="_blank" rel="noopener"
 &gt;Ruff&lt;/a&gt;: extremely fast linter and formatter, replacing Flake8 + isort in most setups.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dependency management:&lt;/strong&gt; &lt;a class="link" href="https://docs.astral.sh/uv/" target="_blank" rel="noopener"
 &gt;uv&lt;/a&gt;: modern, fast package manager replacing pip/virtualenv for most workflows.&lt;/p&gt;
&lt;h2 id="where-i-use-it"&gt;Where I use it
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Infrastructure automation and scripting alongside Ansible&lt;/li&gt;
&lt;li&gt;Data processing and log analysis&lt;/li&gt;
&lt;li&gt;CLI tooling for internal workflows&lt;/li&gt;
&lt;li&gt;Quick API clients and integration scripts&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="resources"&gt;Resources
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://docs.python.org/3/" target="_blank" rel="noopener"
 &gt;docs.python.org&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://realpython.com/" target="_blank" rel="noopener"
 &gt;Real Python&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>