Ansible Data Manipulation with Modules

Ansible loves to pretend that YAML is a programming language. It isn’t. And every engineer who has ever tried to munge data inside a playbook knows the pain. You have filters everywhere, Jinja spaghetti, and tasks that look like they were written during a period of sleep deprivation. How do I know, guilty as charged.

Just to be clear, what i’m saying is YAML and Jinja are not intended to be a Data‑Processing stack

The usual Ansible talking point is that “Ansible is declarative, not imperative”. Sure. But then I immediately need to write imperative logic in Jinja because the playbook layer simply isn’t built for data transformation. I do it, you do it, we’ve all done it. It’s usually quick, and depending on the use case, relatively painless, but at some point, you’ve taken it too far. I know I have, so I’m talking about it now. Automation should be declarative, but you need imperative to achieve declarative – stuff needs to be queried and computed to achieve a desired state. Ansible provides all the batteries needed to achieve this.

The pain points you run into are real, you end up with:

  • Complex list/dict transformations
  • Conditional logic that becomes unreadable in YAML
  • Repeated filter chains that break the moment your data shape changes
  • Playbooks that become untestable because the logic is embedded in templates

Fundamentally, if you’re doing anything non‑trivial with data, YAML based tasks and Jinja are the wrong tool.

Ansible does have a solution: Move the logic into a module

Stop abusing filters, YAML, Jinja. Write a module. If your playbook contains more than two chained filters, or chains of set_facts, or complex jinja, you probably should have written a module.

I’ve written my fair share of modules, they aren’t that difficult, but my mindset has always gone to the convoluted set_fact, conditional, filter, jinja fiasco – because somehow it seems easier at the time. Perhaps it is when you’re trying to capture that initial thought process. But at some point, you need to give yourself a reality check, and maybe it’s just simpler to start with modules than convert later. That’s the thought experiment i’d like you to consider. A module gives you:

  • Real programming constructs
  • Real error handling
  • Real testability
  • Real maintainability
  • Real version control and reuse

Why is this a better Pattern?

Input validation – YAML doesn’t. Playbooks don’t, (don’t say assert to me as i’ve abused that as well). Jinja definitely doesn’t.

Modules let you validate input before you do your thing with it.

Modules are testable

You can unit test a module. You cannot unit test a Jinja filter chain inside a task, and when your processing is a sequence of knitted tasks full of set_facts and recursive playbook calls, you’ve crossed the line into prayer-based testing.

Modules are reusable across roles and playbooks

Copy‑pasting filter chains, or jinja compute, or those wonderful blocks of set_facts and conditionals is how outages happen.

Modules reduce cognitive load

A 20‑line Python function is easier to understand than a 20‑line set_fact, conditional, jinja monstrosity.

The summary is Playbooks orchestrate. Modules compute. This is how Ansible should always have been used.

So, what is my example problem and how do I fix it with modules.

My ansible role was running Proxmox backups in my home lab. I was only backing up systems in my lab that had been powered on since the last backup, either daily or weekly. My pve_backup role was doing all of the following in YAML:

  • Multi‑node API discovery
  • Cross‑node VM enumeration
  • Tag parsing and normalization
  • Per‑VM filtering
  • Per‑VM state evaluation
  • Time‑window logic
  • Task‑history correlation
  • Backup triggering
  • UPID polling
  • Error handling

This is imperative logic. YAML + Jinja is not an imperative language. I had effectively built a Python program using a markup language. Yay me!

Based on my thought process that I describe above, I could identify many ‘code smells’:

Excessive set_fact

This is always a sign the playbook is doing computation, not orchestration.

Nested loops + sub elements

This is a red flag that the data model is too complex for YAML.

Repeated REST calls with identical headers

Modules handle this cleanly; playbooks do not.

Per‑VM include files

This is a workaround for the fact that YAML cannot express real logic.

State accumulation (vms_powered_on_last_week)

This is business logic, not orchestration.

UPID polling in YAML

This is the worst possible place to do it.

Debug statements everywhere

Because debugging YAML logic is hell.

My role wasn’t bad in the terms that it did ‘work’. It’s simply doing something Ansible playbooks were never designed to do.

How did I refactor this mess?

A good module model I use is:

One module = one conceptual operation

My conceptual operations are:

“Given a Proxmox cluster, return the list of VMIDs that should be backed up.”

“Given a VMID, run vzdump and wait for completion.”

That’s it. Two modules replace ~300 lines of gnarly YAML.

Why this is objectively better (oh and I simply feel better about it)

Testable

You can unit‑test the module logic without running Ansible.

Faster

Fewer tasks, leading to fewer forks, leading to fewer HTTP sessions.

Maintainable

No more Jinja filter soup.

Debuggable

You can print structured Python objects, not YAML hacks.

Reusable

Other roles can use the same modules.

Correct abstraction

Playbooks orchestrate. Modules compute.

In summary

Think of your future self now 🙂

Train yourself to spot the above code smells sooner rather than later.

N-4 support for Windows Server Upgrades – Wow?

Windows Server 2025 is well and truly here, and Microsoft is pushing hard to convince you that this is the smoothest upgrade cycle in years. They’re not wrong but is it the whole story. If you’re running 2012 R2, 2016, 2019, or 2022, the new N‑4 in‑place upgrade support means you can jump straight to 2025 in one hop. That’s a big deal. But whether you really can do this is a different question.

If you’re still on 2012 R2, this is your last lifeline.

Media‑Based Upgrade: The Official Story vs Reality

Microsoft claims the ISO‑based upgrade takes “under an hour per server.” Sure, in a lab. With clean images. And no vendor agents. And no ancient NIC drivers. And no weird backup software from 2014. I was able to easily do it under these ideal conditions.

In the real world, which is often quite messy (today’s understatement), expect:

  • Several hours for typical servers – some of these old physical servers take 15 minutes to boot.
  • Longer for anything with third‑party agents, monitoring hooks, or legacy storage drivers
  • Rollback time if something breaks (and something always breaks). Do you have a rollback plan for your in-place upgrade? Does your backup actually work?

Still, the process is quite straightforward: mount ISO —> run setup —> choose “Keep files, settings, and apps” —> Ok, some form of conversation with a deity may help here.

Virtual Machines: The Easy Path (Mostly)

If you’re on Hyper‑V, Microsoft is right: upgrades are usually painless. Hyper‑V integration components update automatically during setup.

If you’re on VMware, Nutanix, Proxmox, or anything else, Microsoft’s advice is blunt but correct:

Update your guest drivers first. Outdated virtualization drivers are the #1 cause of upgrade failures.

Ignore this and you’ll be staring at a recovery console and re-considering your life choices.

Physical Servers: Where Things Get Messy

Microsoft politely hints that your 2012 R2 hardware is “about 15 years old.” Translation: Your server is a fossil. Don’t expect miracles.

You must validate:

  • NIC drivers
  • HBAs
  • RAID/storage controllers
  • PCIe cards
  • Out‑of‑band management firmware

If any of these lack 2025‑compatible drivers, you’re choosing between:

  • Buying new hardware
  • Virtualizing the workload
  • Moving it to the cloud
  • Or hoping

Planning: The Part Everyone Skips Until It’s Too Late

Microsoft’s checklist is great, but incomplete. But really put some thought into these as well …

  • Uninstall old antivirus/EDR agents as they break upgrades constantly
  • Disable third‑party disk encryption
  • Remove legacy monitoring agents (SolarWinds, SCOM, etc.)
  • Disconnect from SANs you don’t need during upgrade
  • Expect to reboot multiple times even after the upgrade completes

The main missing step: Fix whatever broke. Something always breaks.

You cannot in‑place upgrade domain controllers. Don’t even bother trying, let me help you here – no, stop, don’t, you can thank me later

The process for Active Directory upgrades remains the same as it has been for 20 years: Deploy new DC, Replicate, Demote / Promote, Raise Functional Levels.

Licensing: The Part Everyone Forgets – I say everyone, but it was me – I forgot.

If you have Software Assurance, you’re fine. If you rely on KMS, make sure it’s updated — 2025 requires new keys.

If the Upgrade Fails

Microsoft recommends:

  • Checking logs in C:\Windows\Panther
  • Running SetupDiag
  • Restoring from backup

Final Thoughts

If you didn’t test the upgrade on a clone first, you’re already headed for disaster – think of your future self.

You can find more details at Upgrading to Windows Server 2025 from Windows Server 2012 R2, 2016, 2019, or 2022 using Media (ISO) | Microsoft Community Hub

Looking at the Microsoft Agent Framework 1.0

Microsoft Agent Framework 1.0 is the Microsoft’s first production‑level, open‑source platform for building long‑running, autonomous, multi‑agent systems in .NET and Python. It formalises what developers have been improvising for the past year: structured reasoning loops, tool calling, multi‑agent orchestration, and governance. Basically, Microsoft thinks we need to move from ‘research toys’ to ‘cloud‑native automation components’ with deterministic workflows and enterprise boundaries.

What is it?

The framework is not a simple code library! it’s a runtime, SDK and, orchestration fabric:

Why care?

Agents are no longer simplistic ‘chatbots’. Agentic AI is shifting toward long‑running, autonomous systems that collaborate, reason, and operate reliably in production, aligning with the broader industry trend where LLMs are becoming cloud automation components, not fancy UI features.

How? or what’s inside an agentic application

Agents

In the start, Agents were basically just wrappers around various prompts. Now they are stateful runtime components that use LLMs to interpret inputs, call tools and MCP servers, maintain session state, and generate responses. This creates a clean abstraction that aligns well with cloud-native micro-automation.

Workflows

Workflows are deterministic graphs that enforce execution order, coordinate multiple agents, and support both checkpointing and human-in-the-loop interactions. They’re important because LLM’s are non-deterministic, they don’t respond the same way to the same inputs. In a business process determinism can be a key concern.

This separation of concerns is the most important architectural decision which relates ‘Reasoning / Interpretation’ to the Agent, and the overall execution policy and control to the Workflow giving us the best of both worlds.

Middleware

The middleware pipeline allows overall addition of logging, telemetry, filters and compliance logic to our application. Without this, governance typically fails.

Agent to Agent (A2A) communication

Agents can now communicate across runtimes (e.g. From Python to .NET) using a structured protocol.This opens up the multi-language development world for agents.

MCP Integration

Agents can dynamically discover and invoke tools without custom integration code. This defacto industry standard is a must-have for tool usage.

If you’re already in Azure, this will likely become your default agentic automation layer over the next year, or until the next announcements change the playing-field again.

If you’re multi‑cloud, it’s still worth having a look, because this is the direction the entire industry is moving. Frameworks help keep the gun-bearing monkey’s known as LLM’s under control.

AlmaLinux 10 – Install docker-ce

You can read lots of blogs about this. This is the simple minimal version 🙂

Assuming you’re doing everything as root, but if you aren’t then prefix these commands with sudo as appropriate for your environment.

dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
systemctl enable --now docker

That’s it. Done. Now if you want to allow non-root users to use docker.

 usermod -aG docker user   # user is the non-root user you want to be able to use docker

Permanently mount a CIFS share on Linux

Install the required packages

You will need to install the cifs-utils package to mount a network drive on an Ubuntu Linux system.

sudo apt-get install -y cifs-utils

For a Red Hat based system you will also need the cifs-utils package, installed by dnf

sudo dnf install -y cifs-utils

Create a mount point

Create a mount point for the CIFS share. eg.

sudo mkdir /srv/cifs/<share>      # where '<share>' is the name of the share you want mounted

Create a credentials file

sudo vim /etc/credentials.<share>   # where '<share>' is the name of the share you want mounted

Add the following contents to the credentials file, updating with your userid/password details to access your share.

username=<shareuser>
password=<sharepassword>

Make sure the credentials file is only visible to root

sudo chown root:root /etc/credentials.<share>      # where '<share>' is the name of the share you want mounted
sudo chmod 600 /etc/credentials.<share>

Add the mount to /etc/fstab

To mount the network drive permanently, you need to add an entry to the /etc/fstab file to ensure the share is mounted automatically when the system boots.

Open the /etc/fstab file in a text editor and add the following line:

//<ip address of your cifs server>/<share> /srv/cifs/<share> cifs credentials=/etc/credentials.<share> 0 0

Testing the mount

sudo mount -a
df -h


This should show the CIFS share mounted at the specified mount point.

AlmaLinux 9 & Centos 9 Stream builds can now be created for oVirt Nodes

When PR https://github.com/oVirt/ovirt-node-ng-image/pull/146 lands you will be able to build oVirt nodes using AlmaLinux 9 (new) and Centos 9 Stream (fixed).

There are still a few issues with AlmaLinux 9, not because of AlmaLinux, but because the oVirt engine force enables a bunch of Centos 9 Stream repositories breaking the system. You can get around this by basically disabling those repositories in /etc/yum.repos.d/ as they appear – bugs will be filed with the engine.

VS Code on Almalinux 9

Step 1. Add the signing key


sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc

Step 2. Add the repository


echo -e  "[vscode]\nname=packages.microsoft.com\nbaseurl=https://packages.microsoft.com/yumrepos/vscode/\nenabled=1\ngpgcheck=1\nrepo_gpgcheck=1\ngpgkey=https://packages.microsoft.com/keys/microsoft.asc\nmetadata_expire=1h" | sudo tee -a /etc/yum.repos.d/vscode.repo

Step 3. Refresh the yum meta data


sudo dnf update -y

Step 4. Install vscode


sudo dnf install code -y

Microsoft Edge on Almalinux 9

Step 1. Import the key


sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc

Step 2. Add the repos


sudo dnf config-manager --add-repo https://packages.microsoft.com/yumrepos/edge

Step 3. Refresh the yum meta data


sudo dnf update --refresh

Step 4. Install Edge


sudo dnf install microsoft-edge-stable

Microsoft Edge should now be available for you to use either via the CLI or the Gnome Deskop

Compacting WSL hard disk

Like many people I am an extensive user of WSL and Linux under Windows in general. It’s the only real option I have at work and it’s quite a reasonable proposition.

That being said though, the WSL vhdx files can grow as you’re doing Linux work and while you can (and should) clean up side the Linux environment it’s not reflected back to windows as free space.

So how do you compact your WSL file?

  1. Shutdown your WSL system.

    wsl.exe --list --verbose # note the verbose is required to get the state
    Output from wsl.exe to show running WSL environment

    wsl.exe --terminate Ubuntu-24.04
    This shows the output of the wsl command with the instance being terminated
  2. Shrink the disk using diskpart

    diskpart

    The diskpart dialogue is displayed

    You need to select the vhdx file for your WSL instance. The VHDX file is typically found in your AppData folder. In my case it was this.

    Windows explorer show the location of my VHDX file

    I copy the VHDX file location as a path.

    The easiest way to get the full filename + path is to use explorer to copy as path

    DISKPART> select vdisk file="C:\Users\geoff\AppData\Local\Packages\CanonicalGroupLimited.Ubuntu24.04LTS_79rhkp1fndgsc\LocalState\ext4.vhdx"

    Output from selecting the vdisk in diskpart
  3. Compact the vdisk

    DISKPART> compact vdisk

    output of diskpart compact
  4. The results.

    BEFORE

    Explorer vhdx filesize before compact

    AFTER

    Explorer vhdx file size after compact

    As you can see, i’ve freed up nearly 6Gb.

Navigation