Joseph Gagnon
Joseph Gagnon

Reputation: 2115

Multiple expression RegEx in Ansible

Note: I have next to zero experience with Ansible.

I need to be able to conditionally modify the configuration of a Kubernetes cluster control plane service. To do this I need to be able to find a specific piece of information in the file and if its value matches a specific pattern, change the value to something else.

To illustrate, consider the following YAML file:

apiVersion: v1
kind: Pod
metadata:
  labels:
    component: kube-controller-manager
    tier: control-plane
  name: kube-controller-manager
  namespace: kube-system
spec:
  containers:
  - command:
    - kube-controller-manager
    - --authentication-kubeconfig=/etc/kubernetes/controller-manager.conf
    - --authorization-kubeconfig=/etc/kubernetes/controller-manager.conf
    - --bind-address=127.0.0.1
...

In this scenario, the line I'm interested in is the line containing --bind-address. If that field's value is "127.0.0.1", it needs to be changed to "0.0.0.0". If it's already "0.0.0.0", nothing needs to be done. (I could also approach it from the point of view of: if its not "0.0.0.0" then it needs to change to that.)

The initial thought that comes to mind is: just search for "--bind-address=127.0.0.1" and replace it with "--bind-address=0.0.0.0". Simple enough, eh? No, not that simple. What if, for some reason, there is another piece of configuration in this file that also matches that pattern? Which one is the right one?

The only way I can think of to ensure I find the right text to change, is a multiple expression RegEx match. Something along the lines of:

How could I write an Ansible playbook to perform these steps, in sequence and only if each step returns true?

Upvotes: 1

Views: 99

Answers (1)

Vladimir Botka
Vladimir Botka

Reputation: 67984

Read the data from the file into a dictionary

    - include_vars:
        file: conf.yml
        name: conf

gives

  conf:
    apiVersion: v1
    kind: Pod
    metadata:
      labels:
        component: kube-controller-manager
        tier: control-plane
      name: kube-controller-manager
      namespace: kube-system
    spec:
      containers:
      - command:
        - kube-controller-manager
        - --authentication-kubeconfig=/etc/kubernetes/controller-manager.conf
        - --authorization-kubeconfig=/etc/kubernetes/controller-manager.conf
        - --bind-address=127.0.0.1

Update the containers

    - set_fact:
        containers: []
    - set_fact:
        containers: "{{ containers +
                        (update_candidate is all)|ternary([_item], [item]) }}"
      loop: "{{ conf.spec.containers|d([]) }}"
      vars:
        update_candidate:
          - item is contains 'command'
          - item.command is contains 'kube-controller-manager'
          - item.command|select('match', '--bind-address')|length > 0
        update: "{{ item.command|map('regex_replace',
                                     '--bind-address=127.0.0.1',
                                     '--bind-address=0.0.0.0') }}"
        _item: "{{ item|combine({'command': update}) }}"

gives

  containers:
  - command:
    - kube-controller-manager
    - --authentication-kubeconfig=/etc/kubernetes/controller-manager.conf
    - --authorization-kubeconfig=/etc/kubernetes/controller-manager.conf
    - --bind-address=0.0.0.0

Update conf

  conf_update: "{{ conf|combine({'spec': spec}) }}"
  spec: "{{ conf.spec|combine({'containers': containers}) }}"

give

  spec:
    containers:
    - command:
      - kube-controller-manager
      - --authentication-kubeconfig=/etc/kubernetes/controller-manager.conf
      - --authorization-kubeconfig=/etc/kubernetes/controller-manager.conf
      - --bind-address=0.0.0.0

  conf_update:
    apiVersion: v1
    kind: Pod
    metadata:
      labels:
        component: kube-controller-manager
        tier: control-plane
      name: kube-controller-manager
      namespace: kube-system
    spec:
      containers:
      - command:
        - kube-controller-manager
        - --authentication-kubeconfig=/etc/kubernetes/controller-manager.conf
        - --authorization-kubeconfig=/etc/kubernetes/controller-manager.conf
        - --bind-address=0.0.0.0

Write the update to the file

    - copy:
        dest: /tmp/conf.yml
        content: |
          {{ conf_update|to_nice_yaml(indent=2) }}

gives

shell> cat /tmp/conf.yml 
apiVersion: v1
kind: Pod
metadata:
  labels:
    component: kube-controller-manager
    tier: control-plane
  name: kube-controller-manager
  namespace: kube-system
spec:
  containers:
  - command:
    - kube-controller-manager
    - --authentication-kubeconfig=/etc/kubernetes/controller-manager.conf
    - --authorization-kubeconfig=/etc/kubernetes/controller-manager.conf
    - --bind-address=0.0.0.0

Example of a complete playbook for testing

- hosts: localhost

  vars:

    conf_update: "{{ conf|combine({'spec': spec}) }}"
    spec: "{{ conf.spec|combine({'containers': containers}) }}"

  tasks:
    - include_vars:
        file: conf.yml
        name: conf
    - debug:
        var: conf

    - set_fact:
        containers: []
    - set_fact:
        containers: "{{ containers +
                        (update_candidate is all)|ternary([_item], [item]) }}"
      loop: "{{ conf.spec.containers|d([]) }}"
      vars:
        update_candidate:
          - item is contains 'command'
          - item.command is contains 'kube-controller-manager'
          - item.command|select('match', '--bind-address')|length > 0
        update: "{{ item.command|map('regex_replace',
                                     '--bind-address=127.0.0.1',
                                     '--bind-address=0.0.0.0') }}"
        _item: "{{ item|combine({'command': update}) }}"

    - debug:
        var: containers
    - debug:
        var: spec
    - debug:
        var: conf_update

    - copy:
        dest: /tmp/conf.yml
        content: |
          {{ conf_update|to_nice_yaml(indent=2) }}

Upvotes: 1

Related Questions