apiansiblevapix

Only update 100 values simultaneously one api call via Ansible


I've got a .yml file filled with hundreds of configuration values for an Axis network camera. The contents look like this:

---
axis:
  config:
    "Bandwidth.Limit": 0
    "HTTPS.AllowTLS1": "no"
    "HTTPS.AllowTLS11": "no"
    "HTTPS.AllowSSLV3": "no"
    "HTTPS.Ciphers": AES256-SHA:AES128-SHA
    "HTTPS.Enabled": "yes"
    "HTTPS.Port": 443
    ...

The Axis API, called Vapix, provides an update function that updates a value, so I circled through the values and triggered a new API call with every iteration:

---
  - name: update parameters
    local_action:
      module: uri 
      user: "{{ axis_snmp_role.login_user }}"
      password: "{{ axis_snmp_role.login_password }}"
      url: "{{ axis_snmp_role.server_url }}?action=update&{{ item.key }}={{ item.value }}"
      validate_certs: false
    with_dict: "{{ axis.config }}"

Turns out this works, but takes forever. I manually found out that it's possible to update multiple values with one API call by glueing the key/value-pairs together with the &-symbol like this:

https://{{ axis_snmp_role.server_url }}/axis-cgi/param.cgi?action=update&ImageSource.I0.Sensor.ExposureValue=100&Image.I0.Appearance.Compression=50

So I used this to buid one big API call that will set all the values at once.

---
  - name: Create parameter list
    set_fact:
      my_parameters: "{{ my_parameters | default([]) + [item.key + '=' + item.value|string|urlencode() ] }}"
    with_dict: "{{ axis.config }}"

  - name: update parameters
    #no_log: true
    local_action:
      module: uri 
      user: "{{ axis_snmp_role.login_user }}"
      password: "{{ axis_snmp_role.login_password }}"
      url: "{{ axis_snmp_role.server_url }}?action=update&{{ my_parameters | join('&') }}"
      validate_certs: false
    #with_dict: "{{ axis.config }}"

Turns out the network camera doesn't like to get so many values at once, so I guess I am stuck with having to split the process up a bit.

Is it possible to craft an Ansible loop that reads 100 key/values-pairs at once, creates one big api call with all of them, sends it off and repeats this until the end of the config file is reached?


Solution

  • Q: "Is it possible to craft an Ansible loop that reads 100 key/values-pairs at once, creates one big API call with all of them, sends it off and repeats this until the end of the config file is reached?"

    A: Yes. It's possible. Below is a procedure on how to split the parameters. Set the variable period to 100 and test it.

    Given axis.config declare the period for testing

      vars:
        axis:
          config:
            "Bandwidth.Limit": 0
            "HTTPS.AllowTLS1": "no"
            "HTTPS.AllowTLS11": "no"
            "HTTPS.AllowSSLV3": "no"
            "HTTPS.Ciphers": AES256-SHA:AES128-SHA
            "HTTPS.Enabled": "yes"
            "HTTPS.Port": 443
        period: 2
    

    Create a list of parameters and calculate the length of the list

        - set_fact:
            config_list: "{{ config_list|default([]) + [item.key ~ '=' ~ item.value] }}"
          loop: "{{ axis.config|dict2items }}"
        - debug:
            var: config_list
        - set_fact:
            config_length: "{{ config_list|length }}"
        - debug:
            var: config_length
    

    give

        "config_list": [
            "HTTPS.Ciphers=AES256-SHA:AES128-SHA", 
            "HTTPS.AllowTLS11=no", 
            "HTTPS.AllowTLS1=no", 
            "HTTPS.Enabled=yes", 
            "HTTPS.Port=443", 
            "Bandwidth.Limit=0", 
            "HTTPS.AllowSSLV3=no"
        ]
    
    
        "config_length": "7"
    

    Split config_list to sublists of the length period

        - set_fact:
            split_list: "{{ split_list|default([]) +
                            [config_list[split0|int:split1|int]] }}"
          vars:
            split0: "{{ ansible_loop.previtem|default(0) }}"
            split1: "{{ ansible_loop.last|ternary(config_length, item) }}"
          loop: "{{ range(period, config_length|int, period)|list }}"
          loop_control:
            extended: yes
        - debug:
            var: split_list
    

    gives

    
        "split_list": [
            [
                "HTTPS.Ciphers=AES256-SHA:AES128-SHA", 
                "HTTPS.AllowTLS11=no"
            ], 
            [
                "HTTPS.AllowTLS1=no", 
                "HTTPS.Enabled=yes"
            ], 
            [
                "HTTPS.Port=443", 
                "Bandwidth.Limit=0", 
                "HTTPS.AllowSSLV3=no"
            ]
        ]
    

    Loop the list and join the parameters

        - debug:
            msg: "{{ item|join('&') }}"
          loop: "{{ split_list }}"
    

    gives

        "msg": "HTTPS.Ciphers=AES256-SHA:AES128-SHA&HTTPS.AllowTLS11=no"
        "msg": "HTTPS.AllowTLS1=no&HTTPS.Enabled=yes"
        "msg": "HTTPS.Port=443&Bandwidth.Limit=0&HTTPS.AllowSSLV3=no"
    

    With some custom filters (below) the procedure will be simplified

    $ cat filter_plugins/dict_filters.py
    def dict2list(d):
        l = []
        for i in d:
            h = {i:d[i]}
            l.append(h)
        return l
    
    class FilterModule(object):
    
        def filters(self):
            return {
                'dict2list' : dict2list
                }
    
    $ cat filter_plugins/list_filters.py
    def list_split(l, p):
        split_list = []
        for i in range(p, len(l)+p, p):
            if i == p:
                split_list.append(l[0:p])
            elif i > len(l):
                split_list.append(l[j:])
            else:
                split_list.append(l[j:i])
            j = i
        return split_list
    
    class FilterModule(object):
    
        def filters(self):
            return {
                'list_split' : list_split
                }
    
    $ cat filter_plugins/hash_filters.py
    def hash_to_tuple(h):
        return h.items()[0]
    
    class FilterModule(object):
    
        def filters(self):
            return {
                'hash_to_tuple': hash_to_tuple
                }
    

    The tasks below

        - set_fact:
            config_list: "{{ axis.config|
                             dict2list|
                             map('hash_to_tuple')|
                             map('join', '=')|
                             list }}"
    
        - set_fact:
            split_list: "{{ config_list|list_split(period) }}"
    
        - debug:
            msg: "{{ item|join('&') }}"
          loop: "{{ split_list }}"
    

    give the same result

        "msg": "HTTPS.Ciphers=AES256-SHA:AES128-SHA&HTTPS.AllowTLS11=no"
        "msg": "HTTPS.AllowTLS1=no&HTTPS.Enabled=yes"
        "msg": "HTTPS.Port=443&Bandwidth.Limit=0"
        "msg": "HTTPS.AllowSSLV3=no"