Repository: ansible/ansible

Issue:
nxos_ntp_auth issues
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and devel branch are affected too.
Always add information AFTER of these html comments.
-->

##### ISSUE TYPE
<!--- Pick one below and delete the rest -->
 - Bug Report

##### COMPONENT NAME
<!---
Insert below this comment the name of the module, plugin, task or feature.
Do not include extra details here, e.g. "vyos_command" not "the network module vyos_command" or the full path
-->
nxos_ntp_auth
##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes below -->
```
ansible 2.6.0 (devel fed20b825f) last updated 2018/02/15 12:51:12 (GMT -400)
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /root/agents-ci/ansible/lib/ansible
  executable location = /root/agents-ci/ansible/bin/ansible
  python version = 2.7.6 (default, Oct 26 2016, 20:30:19) [GCC 4.8.4]
```

##### OS / ENVIRONMENT
<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say "N/A" for anything that is not platform-specific.
Also mention the specific version of what you are trying to control,
e.g. if this is a network bug the version of firmware on the network device.
-->
Ansible Server : Ubuntu 14.04
Device: N9K running 7.0(3)I2(5)

##### SUMMARY
<!--- Explain the problem briefly -->

##### STEPS TO REPRODUCE
<!---
For bugs, show exactly how to reproduce the problem, using a minimal test-case.
For new features, show how the feature would be used.
-->
There are some issues with nxos_ntp_auth module.
1. When state=absent, authentication is being turned ON if it was turned off which is incorrect. When state=absent, the module should go back to its default state
2. key_id and md5 string are made as 'required' params. If user wants to change only authentication, these are not really required.
3. The module is not idempotent in many cases (for ex. auth_type =encrypt)
4. There is no easy way to remove 'trusted_key'.

Note: I will open a PR shortly to address the above issues
nxos_ntp_auth issues
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and devel branch are affected too.
Always add information AFTER of these html comments.
-->

##### ISSUE TYPE
<!--- Pick one below and delete the rest -->
 - Bug Report

##### COMPONENT NAME
<!---
Insert below this comment the name of the module, plugin, task or feature.
Do not include extra details here, e.g. "vyos_command" not "the network module vyos_command" or the full path
-->
nxos_ntp_auth
##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes below -->
```
ansible 2.6.0 (devel fed20b825f) last updated 2018/02/15 12:51:12 (GMT -400)
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /root/agents-ci/ansible/lib/ansible
  executable location = /root/agents-ci/ansible/bin/ansible
  python version = 2.7.6 (default, Oct 26 2016, 20:30:19) [GCC 4.8.4]
```

##### OS / ENVIRONMENT
<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say "N/A" for anything that is not platform-specific.
Also mention the specific version of what you are trying to control,
e.g. if this is a network bug the version of firmware on the network device.
-->
Ansible Server : Ubuntu 14.04
Device: N9K running 7.0(3)I2(5)

##### SUMMARY
<!--- Explain the problem briefly -->

##### STEPS TO REPRODUCE
<!---
For bugs, show exactly how to reproduce the problem, using a minimal test-case.
For new features, show how the feature would be used.
-->
There are some issues with nxos_ntp_auth module.
1. When state=absent, authentication is being turned ON if it was turned off which is incorrect. When state=absent, the module should go back to its default state
2. key_id and md5 string are made as 'required' params. If user wants to change only authentication, these are not really required.
3. The module is not idempotent in many cases (for ex. auth_type =encrypt)
4. There is no easy way to remove 'trusted_key'.

Note: I will open a PR shortly to address the above issues


Discussion:

Resolving patch:
diff --git a/lib/ansible/modules/network/nxos/nxos_ntp_auth.py b/lib/ansible/modules/network/nxos/nxos_ntp_auth.py
--- a/lib/ansible/modules/network/nxos/nxos_ntp_auth.py
+++ b/lib/ansible/modules/network/nxos/nxos_ntp_auth.py
@@ -34,19 +34,15 @@
     - Jason Edelman (@jedelman8)
 notes:
     - Tested against NXOSv 7.3.(0)D1(1) on VIRL
-    - If C(state=absent), the module will attempt to remove the given key configuration.
-      If a matching key configuration isn't found on the device, the module will fail.
+    - If C(state=absent), the module will remove the given key configuration if it exists.
     - If C(state=absent) and C(authentication=on), authentication will be turned off.
-    - If C(state=absent) and C(authentication=off), authentication will be turned on.
 options:
     key_id:
         description:
             - Authentication key identifier (numeric).
-        required: true
     md5string:
         description:
             - MD5 String.
-        required: true
     auth_type:
         description:
             - Whether the given md5string is in cleartext or
@@ -156,17 +152,19 @@ def get_ntp_auth_key(key_id, module):
     authentication_key = {}
     command = 'show run | inc ntp.authentication-key.{0}'.format(key_id)
     auth_regex = (r".*ntp\sauthentication-key\s(?P<key_id>\d+)\s"
-                  r"md5\s(?P<md5string>\S+).*")
+                  r"md5\s(?P<md5string>\S+)\s(?P<atype>\S+).*")
 
     body = execute_show_command(command, module)[0]
 
     try:
         match_authentication = re.match(auth_regex, body, re.DOTALL)
         group_authentication = match_authentication.groupdict()
-        key_id = group_authentication["key_id"]
-        md5string = group_authentication['md5string']
-        authentication_key['key_id'] = key_id
-        authentication_key['md5string'] = md5string
+        authentication_key['key_id'] = group_authentication['key_id']
+        authentication_key['md5string'] = group_authentication['md5string']
+        if group_authentication['atype'] == '7':
+            authentication_key['auth_type'] = 'encrypt'
+        else:
+            authentication_key['auth_type'] = 'text'
     except (AttributeError, TypeError):
         authentication_key = {}
 
@@ -200,10 +198,11 @@ def auth_type_to_num(auth_type):
 
 def set_ntp_auth_key(key_id, md5string, auth_type, trusted_key, authentication):
     ntp_auth_cmds = []
-    auth_type_num = auth_type_to_num(auth_type)
-    ntp_auth_cmds.append(
-        'ntp authentication-key {0} md5 {1} {2}'.format(
-            key_id, md5string, auth_type_num))
+    if key_id and md5string:
+        auth_type_num = auth_type_to_num(auth_type)
+        ntp_auth_cmds.append(
+            'ntp authentication-key {0} md5 {1} {2}'.format(
+                key_id, md5string, auth_type_num))
 
     if trusted_key == 'true':
         ntp_auth_cmds.append(
@@ -224,25 +223,22 @@ def set_ntp_auth_key(key_id, md5string, auth_type, trusted_key, authentication):
 
 def remove_ntp_auth_key(key_id, md5string, auth_type, trusted_key, authentication):
     auth_remove_cmds = []
-    auth_type_num = auth_type_to_num(auth_type)
-    auth_remove_cmds.append(
-        'no ntp authentication-key {0} md5 {1} {2}'.format(
-            key_id, md5string, auth_type_num))
+    if key_id:
+        auth_type_num = auth_type_to_num(auth_type)
+        auth_remove_cmds.append(
+            'no ntp authentication-key {0} md5 {1} {2}'.format(
+                key_id, md5string, auth_type_num))
 
-    if authentication == 'on':
+    if authentication:
         auth_remove_cmds.append(
             'no ntp authenticate')
-    elif authentication == 'off':
-        auth_remove_cmds.append(
-            'ntp authenticate')
-
     return auth_remove_cmds
 
 
 def main():
     argument_spec = dict(
-        key_id=dict(required=True, type='str'),
-        md5string=dict(required=True, type='str'),
+        key_id=dict(type='str'),
+        md5string=dict(type='str'),
         auth_type=dict(choices=['text', 'encrypt'], default='text'),
         trusted_key=dict(choices=['true', 'false'], default='false'),
         authentication=dict(choices=['on', 'off']),
@@ -264,6 +260,10 @@ def main():
     authentication = module.params['authentication']
     state = module.params['state']
 
+    if key_id:
+        if not trusted_key and not md5string:
+            module.fail_json(msg='trusted_key or md5string MUST be specified')
+
     args = dict(key_id=key_id, md5string=md5string,
                 auth_type=auth_type, trusted_key=trusted_key,
                 authentication=authentication)
@@ -280,18 +280,20 @@ def main():
     if state == 'present':
         if delta:
             command = set_ntp_auth_key(
-                key_id, md5string, auth_type, trusted_key, delta.get('authentication'))
+                key_id, md5string, delta.get('auth_type'),
+                delta.get('trusted_key'), delta.get('authentication'))
             if command:
                 commands.append(command)
     elif state == 'absent':
-        if existing:
-            auth_toggle = None
-            if authentication == existing.get('authentication'):
-                auth_toggle = authentication
-            command = remove_ntp_auth_key(
-                key_id, md5string, auth_type, trusted_key, auth_toggle)
-            if command:
-                commands.append(command)
+        auth_toggle = None
+        if existing.get('authentication') == 'on':
+            auth_toggle = True
+        if not existing.get('key_id'):
+            key_id = None
+        command = remove_ntp_auth_key(
+            key_id, md5string, auth_type, trusted_key, auth_toggle)
+        if command:
+            commands.append(command)
 
     cmds = flatten_list(commands)
     if cmds:


Test patch:
diff --git a/test/integration/targets/nxos_ntp_auth/tests/common/sanity.yaml b/test/integration/targets/nxos_ntp_auth/tests/common/sanity.yaml
--- a/test/integration/targets/nxos_ntp_auth/tests/common/sanity.yaml
+++ b/test/integration/targets/nxos_ntp_auth/tests/common/sanity.yaml
@@ -17,9 +17,7 @@
     nxos_ntp_auth: &configure_text
       key_id: 32
       md5string: hello
-      auth_type: text
-      trusted_key: true
-      authentication: on
+      authentication: off
       state: present
       provider: "{{ connection }}"      
     register: result
@@ -28,21 +26,11 @@
       that:
         - "result.changed == true"
 
-  - name: "Check Idempotence - Configure text ntp authentication"
-    nxos_ntp_auth: *configure_text
-    register: result
-
-  - assert: &false
-      that:
-        - "result.changed == false"
-
   - name: Remove text ntp authentication
     nxos_ntp_auth: &remove_text
       key_id: 32
       md5string: hello
-      auth_type: text
-      trusted_key: true
-      authentication: on
+      authentication: off
       state: absent
       provider: "{{ connection }}"      
     register: result
@@ -54,8 +42,6 @@
       key_id: 32
       md5string: hello
       auth_type: encrypt
-      trusted_key: true
-      authentication: on
       state: present
       provider: "{{ connection }}"      
     register: result
@@ -66,6 +52,70 @@
     nxos_ntp_auth: *configure_encrypt
     register: result
 
+  - assert: &false
+      that:
+        - "result.changed == false"
+
+  - name: Turn on authentication
+    nxos_ntp_auth: &authon
+      authentication: on
+      state: present
+      provider: "{{ connection }}"
+    register: result
+
+  - assert: *true
+
+  - name: "Check Idempotence - Turn on authentication"
+    nxos_ntp_auth: *authon
+    register: result
+
+  - assert: *false
+
+  - name: Turn off authentication
+    nxos_ntp_auth: &authoff
+      authentication: off
+      state: present
+      provider: "{{ connection }}"
+    register: result
+
+  - assert: *true
+
+  - name: "Check Idempotence - Turn off authentication"
+    nxos_ntp_auth: *authoff
+    register: result
+
+  - assert: *false
+
+  - name: Add trusted key
+    nxos_ntp_auth: &tkey
+      key_id: 32
+      trusted_key: true
+      state: present
+      provider: "{{ connection }}"
+    register: result
+
+  - assert: *true
+
+  - name: "Check Idempotence - Add trusted key"
+    nxos_ntp_auth: *tkey
+    register: result
+
+  - assert: *false
+
+  - name: Remove trusted key
+    nxos_ntp_auth: &rtkey
+      key_id: 32
+      trusted_key: false
+      state: present
+      provider: "{{ connection }}"
+    register: result
+
+  - assert: *true
+
+  - name: "Check Idempotence - Remove trusted key"
+    nxos_ntp_auth: *rtkey
+    register: result
+
   - assert: *false
 
   - name: Remove encrypt ntp authentication
@@ -73,7 +123,6 @@
       key_id: 32
       md5string: hello
       auth_type: encrypt
-      trusted_key: true
       authentication: on
       state: absent
       provider: "{{ connection }}"
@@ -81,6 +130,12 @@
 
   - assert: *true
 
+  - name: "Check Idempotence - Remove encrypt ntp authentication"
+    nxos_ntp_auth: *remove_encrypt
+    register: result
+
+  - assert: *false
+
   always:
   - name: Cleanup ntp auth config
     nxos_ntp_auth: *setup