nautobot / nautobot-ansible

Ansible Collection for managing Nautobot Data
https://nautobot-ansible.readthedocs.io/en/latest/
GNU General Public License v3.0
40 stars 31 forks source link

Entry is constantly changed for fields with the type integer #315

Open pugnacity opened 5 months ago

pugnacity commented 5 months ago
ISSUE TYPE
SOFTWARE VERSIONS
pynautobot

2.0.2

Ansible:

2.16.2

Nautobot:

21.1

Collection:

5.1.0

SUMMARY

Fields from type interger are reported as changed on every run.

STEPS TO REPRODUCE
    - name: Init InfraDB | unique-rules
      networktocode.nautobot.plugin:
        url: "{{ nautobot_url }}"
        token: "{{ nautobot_token }}"
        validate_certs: "{{ documentation_nautobot['validate_certs'] }}"
        plugin: nautobot-data-validation-engine
        endpoint: unique-rules
        identifiers:
          name: "{{ rule['name'] }}"
        attrs:
          name: "{{ rule['name'] }}"
          content_type: "{{ rule['content_type'] }}"
          enabled: "{{ rule['enabled'] }}"
          error_message: "{{ rule['error_message'] }}"
          max_instances: "{{ rule['max_instances'] | int }}"
          field: "{{ rule['field'] }}"
        state: present
      loop: "{{ infradb_unique_rules }}"
      loop_control:
        loop_var: rule
      tags: rule

This reports on every run:

    "ansible_loop_var": "rule",
    "changed": true,
    "diff": {
        "after": {
            "max_instances": "1"
        },
        "before": {
            "max_instances": 1
        }
    },

I've the same problem with custom fields for a device, here is the type also integer.

EXPECTED RESULTS

no change after creation

jvanderaa commented 5 months ago

First thing that comes to mind is that this is related to Ansible's continuous challenge with integers. Will take a look at this yet. Just a thought.

joewesch commented 4 months ago

First thing that comes to mind is that this is related to Ansible's continuous challenge with integers. Will take a look at this yet. Just a thought.

Yes, I think this is just Ansible/Jinja2 always outputting it to a string. Here is an example debug task that I created for POC:

    - debug:
        msg: "{{ rule['max_instances'] | int }}"
      loop: "{{ rules }}"
      loop_control:
        loop_var: rule
      vars:
        rules:
          - max_instances: "1"
          - max_instances: 1

And here is the output:

ok: [localhost] => (item={'max_instances': '1'}) => {
    "msg": "1"
}
ok: [localhost] => (item={'max_instances': 1}) => {
    "msg": "1"
}

You can see the item= in the output has both forms of '1' and 1 and it still outputs a string in both.

joewesch commented 4 months ago

@pugnacity what happens if you pass in the rule dict directly to attrs?

    - name: Init InfraDB | unique-rules
      networktocode.nautobot.plugin:
        url: "{{ nautobot_url }}"
        token: "{{ nautobot_token }}"
        validate_certs: "{{ documentation_nautobot['validate_certs'] }}"
        plugin: nautobot-data-validation-engine
        endpoint: unique-rules
        identifiers:
          name: "{{ rule['name'] }}"
        attrs: "{{ rule }}"
        state: present
      loop: "{{ infradb_unique_rules }}"
      loop_control:
        loop_var: rule
      tags: rule

If we can find a way to send in the dict directly instead of unpacking each key the nested keys should remain their respective types instead of being cast to string.

pugnacity commented 4 months ago

@joewesch for this specific case it works, but I've the same problem with networktocode.nautobot.device and networktocode.nautobot.cluster

Even when using the following task:

        - name: Import InfraDB | ESXi | nautobot | device
          networktocode.nautobot.device:
            url: "{{ nautobot_url }}"
            token: "{{ nautobot_token }}"
            name: "{{ inventory_hostname }}"
            device_type: "{{ nautobot_model_name }}"
            platform: "{{ ansible_distribution }}"
            role: "{{ role }}"
            cluster: "{{ cluster_name }}"
            location: "{{ my_location['key'] }}"
            serial: "{{ ansible_product_serial }}"
            status: Active
            custom_fields: "{{ custom_fields }}"
            tags:
              - compute
            state: present
            validate_certs: "{{ documentation_nautobot['validate_certs'] }}"
          register: _nautobot_device
          until: _nautobot_device is succeeded
          retries: "{{ nautobot['retries'] }}"
          delay: "{{ nautobot['delay'] }}"
          delegate_to: localhost
          vars:
            # yamllint disable-line rule:line-length
            my_location: "{{ lookup('networktocode.nautobot.lookup', 'locations', validate_certs=documentation_nautobot.validate_certs, api_endpoint=nautobot_url, token=nautobot_token, api_filter='name=' ~ placement.room ) }}"

The change looks like this

--- before
+++ after
@@ -1,11 +1,11 @@
 {
     "custom_fields": {
-        "memory": 1047129,
-        "number_of_cores": 32,
-        "number_of_cpus": 2
+        "memory": "1047129",
+        "number_of_cores": "32",
+        "number_of_cpus": "2"
     }
 }
joewesch commented 4 months ago

Ok, let me investigate. I wonder if we need to switch custom_fields to raw instead of dict.

joewesch commented 4 months ago

@pugnacity I am not able to replicate this. I was able to get it to pass an integer for number_of_cpus with the following example:

    - networktocode.nautobot.device:
        url: "{{ nautobot_url }}"
        token: "{{ nautobot_token }}"
        name: "slc01-leaf-08"
        custom_fields: "{{ custom_fields }}"
        state: present
      vars:
        custom_fields:
          number_of_cpus: 0

# First run
--- before
+++ after
@@ -1,5 +1,5 @@
 {
     "custom_fields": {
-        "number_of_cpus": null
+        "number_of_cpus": 4
     }
 }

changed: [localhost] => {
    "changed": true,
    ....
    "diff": {
        "after": {
            "custom_fields": {
                "number_of_cpus": 4
            }
        },
        "before": {
            "custom_fields": {
                "number_of_cpus": null
            }
        }
    },

# Second run
ok: [localhost] => {
    "changed": false,

You didn't provide what "{{ custom_fields }}" is in your example, but if you derive the memory, number_of_cores or number_of_cpus with jinja syntax it's always going to output a string.

pugnacity commented 4 months ago

@joewesch the custom_fields looks like this:

custom_fields:
  memory: "{{ (vmware_host_facts['ansible_facts']['ansible_memtotal_mb'] / 1000) | round | int }}"
  number_of_cores: "{{ vmware_host_facts['ansible_facts']['ansible_processor_cores'] | int }}"
  number_of_cpus: "{{ vmware_host_facts['ansible_facts']['ansible_processor_count'] | int }}"

when defining static variables everything is fine, but when used with jinja it seems to be always a string

joewesch commented 4 months ago

but when used with jinja it seems to be always a string

Correct, and this is a limitation with jinja and not something we can inherently control with this collection. Because plugin attributes (and custom fields) can be a myriad of types we don't currently have the ability to determine their type or schema programmatically.