Skip to content

Python inside Ansible Playbook

Avatar photo


I want to share some tips on how to directly use Python inside an Ansible playbook.

Python String Method

Ooops, error: “template error while templating string: no filter named ‘split’…”. This error is not abnormal when you execute the below task.

Normally it is caused by the plugin filter setup.

  vars:
    s1: "Abc,Efg"

  tasks:
  - name: convert string to list
    set_fact:
      list_01: "{{ s1 | split(',') }}"

Is there any quick workaround?

The answer is to directly use the Python string method “split()”:

  tasks:
  - name: convert string to list
    set_fact:
      list_02: "{{ s1.split(',') }}"

The below simple playbook demonstrates more of the Python string methods. “upper()”, “replace()”, “count()” and “isdigit()”.

Directly using Python string methods can improve the playbook string handling capability.

- hosts: localhost
  connection: local
  gather_facts: no
  vars:
    s1: "red hat, red hat"
    d1: '123'
  tasks:
  - name: Python String Methods
    debug: 
      msg: 
      - "Original String s1 - {{ s1 }}; d1 - {{ d1 }}"
      - "Converts a string into upper case - s1.upper(): {{ s1.upper() }}"
      - "Converts a string into upper case - s1.replace('r','R').replace('h','H'): {{ s1.replace('r','R').replace('h','H') }}"
      - "Returns the number of times a specified value occurs in a string - s1.count('red'): {{ s1.count('red') }}"
      - "Returns True if all characters in the string are digits - d1.isdigit(): {{ d1.isdigit() }}"

The output of the playbook:

PLAY [localhost] ****************************************************************************************************************************************************

TASK [Python String Methods] ****************************************************************************************************************************************
ok: [localhost] => {
    "msg": [
        "Original String s1 - red hat, red hat; d1 - 123",
        "Converts a string into upper case - s1.upper(): RED HAT, RED HAT",
        "Converts a string into upper case - s1.replace('r','R').replace('h','H'): Red Hat, Red Hat",
        "Returns the number of times a specified value occurs in a string - s1.count('red'): 2",
        "Returns True if all characters in the string are digits - d1.isdigit(): True"
    ]
}

PLAY RECAP **********************************************************************************************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Python print() Method

Let’s view the below use case. It is part of the requirements to auto-generate the hostname during VM provision.

Convert ‘numeric’ index to 3 character string:

  • 1 ⇒ 001
  • 10 ⇒ 010
  • 100 ⇒ 100

Standard Ansible code looks like this. It includes three tasks.

- hosts: localhost
  connection: local
  gather_facts: no

  vars:
    vm_index: 10

  tasks:
  - name: index less 10
    debug:
      msg: "{{ '00' + vm_index | string }}"
    when: vm_index < 10

  - name: index less 100
    debug:
      msg: "{{ '0' + vm_index | string }}"
    when: 
    - vm_index < 100
    - vm_index >= 10

  - name: index less 1000
    debug:
      msg: "{{ vm_index | string }}"
    when: 
    - vm_index < 1000
    - vm_index >= 100

It can be simplified using Python if statement. It includes one task.

  - name: index string
    debug:
      msg: "{{ '00' + vm_index | string
               if vm_index < 10 
               else '0' + vm_index | string 
               if vm_index < 100 
               else vm_index 
               if vm_index < 999 
               else '---' }}"

Can we make it better? Yes, using Python print() method.

Simpler, nicer.

  - name: index string
    debug:
      msg: "{{ '%03d' % vm_index }}"

Python Code

Sometimes, the Ansible module is not available for certain tasks. This task can be done within Python with less than 10 lines of code but to write a customized Python module would incur too much “overhead” work. In such a scenario, I prefer to extract and use the Python code inside the playbook.

One use case is to convert the SGT time to UTC time. I was not able to find the available Ansible module for such a simple task. But I know it can be done easily with a few Python codes. I used the Python code inside the playbook to solve this problem.

The server’s time zone is “Asia/Singapore”. Below is the playbook. It demonstrates how to directly use Python code and how to pass the input and output value between Python code and playbook.

  - debug:
          msg: "Singapore Time: {{ year }}-{{ month }}-{{ date }}T{{ hour }}:{{ min }}:{{ sec }}"

  # it is easy to do time conversion in the python
  # Below is to directly use python code for time conversion
  # It requires tzdata packge and environment TZ is SGT
  - name: python code converts SGT to UTC
    command: /usr/bin/python3
    args:
      stdin: |
        from datetime import datetime
        import pytz

        dt_str  = "{{ year }}{{ month }}{{ date }}T{{ hour }}{{ min }}{{ sec }}"
        format = "%Y%m%dT%H%M%S"
        local_dt = datetime.strptime(dt_str, format)

        dt_utc = local_dt.astimezone(pytz.UTC)
        format = "%Y-%m-%dT%H:%M:%SZ"
        dt_utc_str = dt_utc.strftime(format)

        print(dt_utc_str)
    register: results

  - set_fact:
      utc_date: "{{ results.stdout }}"

Disclaimer: The views expressed and the content shared are those of the author and do not reflect the views of the author’s employer or techbeatly platform.

Avatar photo

I’m Jin, Red Hat ASEAN Senior Platform Consultant. My primary focus is Ansible Automation (Infrastructure as Code), OpenShift, and OpenStack.

Comments

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.