Category: Ansible

Molecule + Podman: Why It’s a Pain, but here’s an Example That Finally Works

If you’ve ever tried to run Molecule with Podman instead of Docker, you already know the truth: it never “just works.” Between dynamic inventories, missing Python interpreters, inconsistent container images, and Molecule’s habit of generating YAML that looks like it was written by a committee, you spend more time debugging the framework than testing your Ansible roles.

This post walks through a real, working Molecule + Podman setup that supports multiple OS containers (Ubuntu, Rocky, CentOS Stream), uses Podman natively, and avoids the usual landmines:

  • Python missing inside containers
  • Fact‑gathering failures
  • Molecule’s dynamic inventory breaking
  • Destroy steps leaving orphaned containers
  • Podman connection plugin quirks

If you’ve been fighting Molecule + Podman, this example will save you hours.

The Problem: Molecule + Podman Is Not a Drop‑In Replacement for Docker

Molecule’s Docker driver is mature. Podman’s driver… well… it isn’t.

If you’re testing roles across multiple OSes, these problems multiply and you eventually just give up.

The Working Example: Multi‑OS Molecule Scenario Using Podman

Here’s the structure:

molecule/default/
├── create.yml
├── prepare.yml
├── converge.yml
├── verify.yml
├── destroy.yml
├── molecule.yml
└── requirements.yml

Key idea:

We start our own containers with podman and generate our own dynamic inventory inside create.yml and force Molecule to use it.

This avoids Molecule’s broken Podman inventory plugin entirely.

1. create.yml Start Containers + Build Inventory

We define our Podman containers, start them and force feed the inventory to molecule:

- name: Create
  hosts: localhost
  gather_facts: false
  vars:
    molecule_inventory:
      all:
        hosts: {}
        molecule: {}
    
    podman:
      - name: ubuntu-2404
        image: ubuntu:24.04
        command: /bin/bash
        tty: true
      - name: rocky9
        image: quay.io/rockylinux/rockylinux:9  
        command: /bin/bash
        tty: true
      - name: rocky10
        image: quay.io/rockylinux/rockylinux:10
        command: /bin/bash
        tty: true
      - name: cs9
        image: quay.io/centos/centos:stream9
        command: /bin/bash
        tty: true
  tasks:

    - name: Start Podman container(s)
      containers.podman.podman_container:
        name: "{{ item.name }}"
        image: "{{ item.image }}"
        state: started
        command: "{{ item.command | default(omit) }}"
        tty: "{{ item.tty | default(omit) }}"
      register: result
      loop: "{{ podman }}"
      loop_control:
        label: "{{ item.name }}"

    - name: Add container to molecule_inventory
      vars:
        inventory_partial_yaml: |
          all:
            children:
              molecule:
                hosts:
                  "{{ item.name }}":
                    ansible_connection: containers.podman.podman
      ansible.builtin.set_fact:
        molecule_inventory: >
          {{ molecule_inventory | combine(inventory_partial_yaml | from_yaml, recursive=true) }}
      loop: "{{ podman }}"
      loop_control:
        label: "{{ item.name }}"

    - name: Dump molecule_inventory
      ansible.builtin.copy:
        content: |
          {{ molecule_inventory | to_yaml }}
        dest: "{{ molecule_ephemeral_directory }}/inventory/molecule_inventory.yml"
        mode: "0600"

    - name: Force inventory refresh
      ansible.builtin.meta: refresh_inventory

    - name: Fail if molecule group is missing
      ansible.builtin.assert:
        that: "'molecule' in groups"
        fail_msg: |
          molecule group was not found inside inventory groups: {{ groups }}
      run_once: true # noqa: run-once[task]

# we want to avoid errors like "Failed to create temporary directory"
- name: Validate that inventory was refreshed
  hosts: molecule
  gather_facts: false
  tasks:
    - name: Check uname
      ansible.builtin.raw: uname -a
      register: result
      changed_when: false

    - name: Display uname info
      ansible.builtin.debug:
        msg: "{{ result.stdout }}"

It’s important to note, we added our containers to the inventory with the podman ansible_connection, containers.podman.podman_container.

Out inventory file under the covers looks like this:

all:
  children:
    molecule:
      hosts:
        ubuntu-2404:
          ansible_connection: containers.podman.podman
        rocky9:
          ansible_connection: containers.podman.podman
        rocky10:
          ansible_connection: containers.podman.podman
        cs9:
          ansible_connection: containers.podman.podman

And note that we force Molecule to refresh:

- meta: refresh_inventory

This is the magic step most examples miss.

2. prepare.yml Only Touch Ubuntu Containers

Instead of conditionals, we use a host pattern and fix the missing python3 in the Ubuntu containers.

- hosts: "ubuntu*"
  gather_facts: false
  tasks:
    - name: Refresh apt cache Ubuntu container
      raw: |
        apt-get update
    - name: Install Python inside Ubuntu container
      raw: |
        apt-get install -y python3
    - name: Set correct Python interpreter for Ubuntu
      set_fact:
        ansible_python_interpreter: /usr/bin/python3

Why this works:

  • Molecule names your Ubuntu containers ubuntu-*
  • No need for OS detection
  • No need for fact gathering

This is the cleanest possible prepare step.

3. converge.yml Run Tasks on All Containers

This is the thing we’re testing

- hosts: all
  tasks:
    - shell: "cat /etc/*release*"
      register: result
    - debug: msg="{{ result }}"

Simple, predictable, works across all OSes or well, it does what you want your code to do.

4. verify.yml OS‑Specific Assertions

- hosts: rocky9
  tasks:
    - assert:
        that:
          - "'dnf' in ansible_facts.packages"

- hosts: ubuntu-2404
  tasks:
    - assert:
        that:
          - "'apt' in ansible_facts.packages"

This is how you validate OS‑specific behaviour cleanly, again, you do you.

5. destroy.yml Clean Container Removal

- name: Destroy molecule containers
  hosts: molecule
  gather_facts: false
  tasks:
    - name: Delete Podman container(s)
      containers.podman.podman_container:
        name: "{{ item }}"
        state: absent
      loop: "{{ groups['all'] | difference(['localhost']) }}"
      delegate_to: localhost

- name: Remove dynamic molecule inventory
  hosts: localhost
  gather_facts: false
  tasks:

    - name: Remove dynamic inventory file
      ansible.builtin.file:
        path: "{{ molecule_ephemeral_directory }}/inventory/molecule_inventory.yml"
        state: absent

This avoids:

  • Molecule leaving orphaned containers
  • Podman complaining about missing names
  • localhost being treated as a container

Why This Example Works When Others Don’t

1. We bypass Molecule’s broken Podman inventory plugin

We generate our own containers and inventory and force Molecule to use it.

2. We use host patterns instead of OS detection

Fact gathering doesn’t work until Python is installed.

3. We use Podman’s connection plugin correctly

Every host gets:

ansible_connection: containers.podman.podman

4. We refresh inventory explicitly

Without this, Molecule uses stale inventory.

Conclusion: Molecule + Podman Is Painful …. But Fixable

Podman is great. Molecule is great. Together? They fight.

But with:

  • explicit container creation
  • explicit inventory generation
  • host patterns
  • correct Podman connection plugin
  • correct Ubuntu prepare steps
  • correct destroy logic

You can run a multi‑OS Molecule test matrix under Podman reliably.

This example proves it.

If you’re building cloud automation, CI pipelines, or multi‑distro Ansible roles, this pattern will save you hours of frustration.

The Two Patterns of Ansible Automation

Pipeline Execution vs Controller Execution

Most teams don’t realize they’re choosing an automation architecture every time they run a playbook. They think they’re choosing a tool. They’re actually choosing a pattern.

In my opinion, there are only two patterns that matter:

  1. Pipeline‑Driven Execution
  2. Controller‑Driven Execution (AWX/AAP)

Both work. Both fail. Both solve different problems.

This article breaks down the patterns, the guidance for using them, the anti‑patterns that cause outages, and when combining them actually makes sense.

Pattern 1: Pipeline‑Driven Execution

“Automation lives in Git. Execution happens in CI.”

This is the alleged cloud‑native. I hate the term ‘cloud-native’, it’s just a modern pattern and can equally apply to on-premises, but I’ll use it as it’s become an industry norm.

The pipeline:

  • checks out the repo
  • runs ansible-playbook directly
  • injects secrets at runtime
  • uses inventories stored in Git
  • uses vars stored in Git
  • uses execution environments defined in Git
  • tests automation before deploying
  • runs deterministically and reproducibly

This pattern treats Ansible like any other automation tool: code —> test —> deploy —> verify.

When to use it

Pipeline execution is the right answer when you need:

  • reproducibility
  • portability
  • deterministic execution
  • Git as the single source of truth
  • cloud‑neutral automation
  • ephemeral runners
  • multi‑cloud or hybrid cloud
  • developer‑friendly workflows
  • automation that can run anywhere

This is the pattern used by ‘cloud‑native’ teams, platform teams, and anyone who values Git truth over controller truth.

Strengths

  • Fully reproducible
  • Fully portable
  • Fully version‑controlled
  • No hidden state
  • No GUI configuration
  • No controller dependency
  • Works in any CI/CD system
  • Works locally
  • Works in DR
  • Works in multi‑cloud

Weaknesses

  • Pipelines must understand environment boundaries
  • Pipelines must manage secrets securely
  • Pipelines must enforce guardrails
  • Pipelines can become too flexible
  • Pipelines can accidentally bypass governance

This is why enterprises often avoid this pattern, not because it’s wrong, but because they fear losing control. The fear is real.

Pattern 2: Controller‑Driven Execution (AWX/AAP)

“Automation runs inside a governed platform.”

This is the typical enterprise pattern.

AWX/AAP:

  • pulls playbooks from Git
  • stores inventories
  • stores credentials
  • enforces RBAC
  • provides audit trails
  • standardizes execution environments
  • triggers automation from events
  • provides multi‑team visibility

This pattern treats Ansible as a governed automation platform, not a CLI tool.

When to use it

Controller execution is the right answer when you need:

  • RBAC
  • credential isolation
  • auditability
  • inventory synchronization
  • standardized execution environments
  • event‑driven automation
  • multi‑team governance
  • compliance and regulatory controls

This is the pattern used by large enterprises, regulated industries, and teams with strict governance requirements.

Strengths

  • Centralized governance
  • Centralized credentials
  • Centralized inventory
  • Centralized audit
  • Standardized execution environments
  • Event‑driven automation
  • Multi‑team visibility
  • Strong guardrails

Weaknesses

  • Hidden state
  • GUI configuration
  • Non‑portable automation
  • Not fully reproducible
  • Not fully version‑controlled
  • Controller dependency
  • Harder to test automation before deployment
  • Harder to run automation outside AWX

This is why ‘cloud‑native’ teams avoid this pattern, not because it’s wrong, but because it’s not portable.

Anti‑Patterns

These are the patterns that break automation, cause outages, and create drift.

Anti‑Pattern 1: AWX as the “automation store”

Pipelines call AWX job templates as if AWX stores automation.

It doesn’t.

AWX stores configuration, not automation.

Automation lives in Git. AWX overrides Git with controller‑side state.

This creates split‑brain automation, if you think git is the source of truth, surprise, it isn’t.

Anti‑Pattern 2: Pipelines storing credentials

This is a governance failure.

Pipelines should inject secrets at runtime, not store them.

AWX should manage credentials. Actually, just about anything else should manage credentials.

Anti‑Pattern 3: AWX storing inventories that drift from Git

Inventories should be version‑controlled.

If AWX is the inventory source of truth, Git is no longer authoritative.

This breaks reproducibility.

Anti‑Pattern 4: Pipelines bypassing RBAC

If pipelines can run any playbook against any host using any credential, you’ve lost governance.

This is dangerous in enterprise environments.

Anti‑Pattern 5: AWX storing controller‑side vars that override Git

This creates non‑deterministic execution.

Playbooks behave differently depending on controller configuration.

This breaks portability.

Does the Combination Make Sense?

Here’s the part most teams miss:

The two patterns are not mutually exclusive. They are complementary.

The Practical Guidance

Use pipeline execution when:

  • automation must be reproducible
  • automation must be portable
  • automation must be version‑controlled
  • automation must run anywhere
  • automation must be tested before deployment

Use controller execution when:

  • governance matters
  • RBAC matters
  • credential isolation matters
  • auditability matters
  • inventory sync matters
  • event‑driven automation matters

Use both when:

  • you need reproducibility and governance
  • you need portability and control
  • you need Git truth and environment truth
  • you need cloud‑native execution and enterprise guardrails

This is the two‑layer architecture.

Final Takeaway

There are two patterns.

Trying to make one do both is how teams create drift, outages, and brittle automation.

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.

Milestone 0.1.5 for Hyper-V ansible collection

Milestone 0.1.5 has been achieved for the gocallag.hyperv collection which you can find in ansible galaxy at Ansible Galaxy – gocallag.hyperv.

Release notes for 0.1.5 can be found over at github at Release Milestone 0.1.5 · gocallag/hyperv (github.com)

Further information on upcoming milestones and their associated features can be found at Milestones – gocallag/hyperv (github.com)

What’s Changed

  • Added Feature to vm_info to provide search capability by Name
  • Closed Issue 2 – Feature to allow vm info to search by power state and names
  • BugFIX: Use the convertto-json , convertfrom-json to avoid loop in Exit-Json (limiting depth)
  • Closed Test Issue: Added verifier for vm_info module checks and cleanups
  • Closed Test Issue: Added basic asserts for switch_info testing via molecule
  • Closed Test Issue: Added verifier asserts for switch module as part of molecule testing
  • Closed Test Issue: Added assert based testing for vm module as part of molecule
  • BugFIX, Issue 12: bug vm info fails molecule verifier on running vm check

Feedback / Requests welcome.

Fatal glibc error: CPU does not support x86-64-v2

I’m just writing this down in case anyone has a similar issue.

As per Building Red Hat Enterprise Linux 9 for the x86-64-v2 microarchitecture level | Red Hat Developer, back in 2020, AMD, Intel, Red Hat, and SUSE collaborated to define three x86-64 microarchitecture levels on top of the x86-64 baseline. The three microarchitectures group together CPU features roughly based on hardware release dates:

  • x86-64-v2 brings support (among other things) for vector instructions up to Streaming SIMD Extensions 4.2 (SSE4.2)  and Supplemental Streaming SIMD Extensions 3 (SSSE3), the POPCNT instruction (useful for data analysis and bit-fiddling in some data structures), and CMPXCHG16B (a two-word compare-and-swap instruction useful for concurrent algorithms).
  • x86-64-v3 adds vector instructions up to AVX2, MOVBE (for big-endian data access), and additional bit-manipulation instructions.
  • x86-64-v4 includes vector instructions from some of the AVX-512 variants.

This is a great idea and goal except when you have perfectly good old hardware that, while end-of-life is still working and you find it doesn’t support the new compile target.

This nice little awk script from the fine folks over at stackexchange will show you what microarchitecture your cpu supports by looking at the /proc/cpuinfo flags. I’ve included a local copy here and as you can see it’s pretty simple.

#!/usr/bin/awk -f

BEGIN {
    while (!/flags/) if (getline < "/proc/cpuinfo" != 1) exit 1
    if (/lm/&&/cmov/&&/cx8/&&/fpu/&&/fxsr/&&/mmx/&&/syscall/&&/sse2/) level = 1
    if (level == 1 && /cx16/&&/lahf/&&/popcnt/&&/sse4_1/&&/sse4_2/&&/ssse3/) level = 2
    if (level == 2 && /avx/&&/avx2/&&/bmi1/&&/bmi2/&&/f16c/&&/fma/&&/abm/&&/movbe/&&/xsave/) level = 3
    if (level == 3 && /avx512f/&&/avx512bw/&&/avx512cd/&&/avx512dq/&&/avx512vl/) level = 4
    if (level > 0) { print "CPU supports x86-64-v" level; exit level + 1 }
    exit 1
}

Running the awk script on my test system reveals :

$ ./testarch.awk
CPU supports x86-64-v1

The implications of this are annoying for me. I was trying to get awx to work on my little play system, but as the awx container is based on centos9 and compiled requiring at least x86-64-v2 then the awx container just wont start – yes I know there is more to awx than just this container, but it highlights the point nicely in the following command.

$ docker run --rm  ghcr.io/ansible/awx:latest
Fatal glibc error: CPU does not support x86-64-v2

This seems to have started somewhere after awx release 19.5.0

Ansible Tower provider for Cloudforms / ManageIQ

Always fun to strike problems in what should be the simplest things. I wanted to add Ansible Tower as a service into ManageIQ. Cloudforms would have a similar result.

Following the very simple instructions at https://www.manageiq.org/docs/reference/latest/doc-Managing_Providers/miq/index I proceeded to add the new provider.

It’s all pretty straight forward. Putting in the URL to my Tower server I get greeted with a bunch of errors.

Credential validation was not successful:  {:headers=>{"server"=>"nginx", "date"=>"Mon, 13 Jan 2020  23:42:12 GMT", "content-type"=>"text/html; charset=utf-8",  "content-length"=>"3873", "connection"=>"close",  "vary"=>"Cookie, Accept-Language, Origin",  "content-language"=>"en", "x-api-total-time"=>"0.046s"
.
.
.
blah blah
.
.
status=>404

By default ManageIQ and Cloudforms search for /api/v1 on the Tower server. /api/v1 was deprecated and removed as of Ansible Tower 3.6, see https://docs.ansible.com/ansible-tower/latest/html/towerapi/conventions.html

So, what is a person to do? Hit the google. Eventually I came across this bugzilla item https://bugzilla.redhat.com/show_bug.cgi?id=1740860 and it gave a hint as to just specifying the /api/v2 in the URL I gave to ManageIQ rather than just the base hostname. eg. https://blah…./api/v2

Tried it, it worked! My credential validated and a provider refresh was automatically initiated and all my Ansible Tower templates and inventories were discovered correctly.

Azure Credentials for Ansible

So, you need Ansible to connect to Azure. Congrats, Ansible is awesome for managing Azure resources. The Ansible team has already put together a scenario on how to integrate Ansible with Azure over at https://docs.ansible.com/ansible/latest/scenario_guides/guide_azure.html

The section ‘Authenticating with Azure‘ sounds like the right place, but you can’t use your AD username / password from Ansible because you turned on 2FA – You turned it on RIGHT? So the option left to you is to create a Service Principal (SP).

Note: having 2FA on your account is what you should be doing, so don’t turn it off.

It’s quite simple to create a credential for Ansible to use when connecting to Azure. Simply, fire up the Cloud Shell (awesome feature BTW Microsoft) and create a Service Principal (SP).

But Hang On, what is a Service Principal? The Ansible guide refers you to the Azure documentation over at https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal which you will read, and if you’re like me, you’ll wonder what you actually just read. Have no fear. As I mentioned above you can use a simple Azure CLI command (via the Cloud Shell you just started) and create the Service Principal. Think of the Service Principal as a credential an application (in this case Ansible) can use to access the Azure service(s).

geoff@Azure:~$ az ad sp create-for-rbac --name svc-ansible-azure  # (optional if not specified one will be generated)  --password 'ALovelyComplexPasswor@'
Changing "svc-ansible-azure" to a valid URI of "http://svc-ansible-azure", which is the required format used for service principal names
Creating a role assignment under the scope of "/subscriptions/88888888-4444-4444-4444-cccccccccccc"
  Retrying role assignment creation: 1/36
  Retrying role assignment creation: 2/36
{
  "appId": "appid888-4444-4444-4444-cccccccccccc",
  "displayName": "svc-ansible-azure",
  "name": "http://svc-ansible-azure",
  "password": "password-4444-4444-4444-cccccccccccc",
  "tenant": "tenant88-4444-4444-4444-cccccccccccc"
}
geoff@Azure:~$

If you want to see what that command just did in the Azure portal, head over to the Azure Active Directory -> App registrations blade.

and then you can see the Service Principal you just created.

So what do you do with the new credential.

The Ansible Azure scenario guide has a section on what to do, however, it’s a bit too vague for me.

Using Environment Variables

To pass service principal credentials via the environment, define the following variables:

  • AZURE_CLIENT_ID
  • AZURE_SECRET
  • AZURE_SUBSCRIPTION_ID
  • AZURE_TENANT

Azure has given me :

“appId”: “appid888-4444-4444-4444-cccccccccccc”,
“displayName”: “svc-ansible-azure”,
“name”: “http://svc-ansible-azure”,
“password”: “password-4444-4444-4444-cccccccccccc”,
“tenant”: “tenant88-4444-4444-4444-cccccccccccc”

For your sanity,
AZURE_CLIENT_ID ==> appId
AZURE_SECRET ==> password
AZURE_TENANT ==> tenant

The remaining item, AZURE_SUBSCRIPTION_ID is exactly that, you can also get from the Cloud Shell as follows

geoff@Azure:~$ az account list
[
  {
    "cloudName": "AzureCloud",
    "id": "subscrip-4444-4444-4444-cccccccccccc
    "isDefault": true,
.
.
.

In this case AZURE_SUBSCRIPTION_ID ==> id , whichever id in your account that is valid for your use case.

If you want to add these credentials into Ansible Tower, simply create a Credential of type Microsoft Azure Resource Manager and use the values you’ve deduced above. Ansible Tower will automatically translate them into Environment Variables for your Tower template execution.

Enjoy Ansible and Azure!

Ansible, more than just SSH

I often see the statement Ansible manages clients using SSH or WinRM. While this is a true statement, it is also incomplete.

Ansible currently has 26 connection types which you can find at https://docs.ansible.com/ansible/latest/plugins/connection.html

For me personally, some of the other interesting connection types are :

  • netconf
  • network_cli

    netconf and network_cli are commonly used to perform network device automation.
  • psrp

    psrp is similar to WinRM however it has the added benefit of being used via a proxy which is very useful when you have to consider bastian hosts.
  • vmware_tools

    vmware_tools is a relatively new addition to the ansible family and allows you to execute commands, transfer files to vSphere based systems without using the VM network.

Most Ansible developers will have also used connection type local in many of their playbooks, probably without realizing that it was a different connection type.

Ansible is also extensible. If you need to connect to something weird and wacky (but of great importance to you) then you can develop your own modules and connection plugin (or other sorts of plugins) – see https://docs.ansible.com/ansible/latest/plugins/plugins.html

Ansible versatility doesn’t end there though and many newcomers to ansible don’t realise that you can also manage multiple clouds, container platforms and virtualisation platforms – it’s the Swiss Army knife of IT automation.

Adding a custom credential type in Ansible Tower for ServiceNOW

It’s been one of those weeks and I needed to get some more experience with the ansible ServiceNOW modules, specifically within Ansible Tower. It looked pretty simple and in fact it really was quite simple.

Ansible Tower neatly stores credentials within it – or externally if that fills you with joy. There isn’t a ServiceNow credential type in Ansible Tower. Undeterred, I thought I would use machine credentials, but tower has an annoying behavior of only allowing 1 instance of each credential type attached to a tower template and I am already using machine credentials in my template.

Fortunately on the left hand side of the tower ui there’s an entry labelled credential types

When creating the credential type you need to supply two (2) pieces of information. The first piece is called the Input configuration – or what the fields look like on the web ui when you create a credential of this type and secondly, the Injector configuration which details what do do with thew credentials.

In my case, the new credential type is called SNOW and i’m providing the instance name, username and password as part of the structure for this credential – via the Input configuration and then I detail that I want to store this data in environment variables that will be accessible from my playbook when run in tower.

Input configuration

fields:
  - id: instance
    type: string
    label: Instance
  - id: username
    type: string
    label: Username
  - id: password
    type: string
    label: Password
    secret: true
required:
  - instance
  - username
  - password

Injector configuration

env:
  SN_INSTANCE: '{{instance}}'
  SN_PASSWORD: '{{password}}'
  SN_USERNAME: '{{username}}'

The way you use them in your playbook is quite simple. The following is a snippet of playbook showing that.


   - name: Create an incident
     snow_record:
       username: '{{ lookup("env", "SN_USERNAME") }}'
       password: '{{ lookup("env", "SN_PASSWORD") }}'
       instance: '{{ lookup("env", "SN_INSTANCE") }}'
       state: present
       data:
         short_description: "This is a test incident opened by Ansible"
         severity: 3
         priority: 2
     register: new_incident

Navigation