Repository: ansible/ansible

Issue:
cloud:azure: error while using azure_rm_networkinterface for creating a vnic without public IP
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
- azure_rm_networkinterface.py

##### ANSIBLE VERSION
```
ansible 2.5.0b1
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/home/paolomargara/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /home/paolomargara/ansible/local/lib/python2.7/site-packages/ansible
  executable location = /home/paolomargara/ansible/bin/ansible
  python version = 2.7.12 (default, Jul  1 2016, 15:12:24) [GCC 5.4.0 20160609]
```

##### CONFIGURATION
The output of "ansible-config dump --only-changed" is void.

##### OS / ENVIRONMENT
"N/A"

##### SUMMARY
- When I run a test playbook with the provided example for "Create a network interface with private IP address only (no Public IP)" I got the following error "argument ip_configurations is of type <type 'dict'> and we were unable to convert to list: <type 'dict'> cannot be converted to a list"
- After changing the ip_configurations from a dict to a list to address the previous error I got the following one "Parameter 'public_ip_address_name' can not be None."

##### STEPS TO REPRODUCE
- execute the following command:
```
ansible-playbook azure-deploy-test-vnic.yml
```
With the following playbook:
```yaml
---
- hosts: localhost
  connection: local
  tasks:
  - name: Create a network interface with private IP address only (no Public IP)
    azure_rm_networkinterface:
      name: cloudnode01
      resource_group: test-resourcegroup
      virtual_network_name: test-vnet
      subnet_name: test-subnet
      ip_configurations:
          name: test-cloudnode01
          primary: True
```
Will trigger the first issue.
- Correct the playbook:
```yaml
---
- hosts: localhost
  connection: local
  gather_facts: yes
  tasks:
  - name: Create a network interface with private IP address only (no Public IP)
    azure_rm_networkinterface:
      name: cloudnode01
      resource_group: test-resourcegroup
      virtual_network_name: test-vnet
      subnet_name: test-subnet
      ip_configurations:
        - name: test-cloudnode01
          primary: True
```
Will trigger the second one.
##### EXPECTED RESULTS
The provided example should work as expected.
Should be possible to add a network interface without a public IP address.

##### ACTUAL RESULTS
- In the first case:
```
...
fatal: [localhost]: FAILED! => {
    "changed": false, 
    "failed": true, 
    "invocation": {
        "module_args": {
            "append_tags": true, 
            "ip_configurations": {
                "name": "test-cloudnode01", 
                "primary": true
            }, 
            "name": "cloudnode01", 
            "os_type": "Linux", 
            "private_ip_allocation_method": "Dynamic", 
            "public_ip": true, 
            "public_ip_allocation_method": "Dynamic", 
            "resource_group": "test-resourcegroup", 
            "state": "present", 
            "subnet_name": "test-subnet", 
            "virtual_network_name": "test-vnet"
        }
    }, 
    "msg": "argument ip_configurations is of type <type 'dict'> and we were unable to convert to list: <type 'dict'> cannot be converted to a list"
}
...
```
- In the second one:
```
The full traceback is:
Traceback (most recent call last):
  File "/tmp/ansible_TpE0Hc/ansible_module_azure_rm_networkinterface.py", line 595, in <module>
    main()
  File "/tmp/ansible_TpE0Hc/ansible_module_azure_rm_networkinterface.py", line 591, in main
    AzureRMNetworkInterface()
  File "/tmp/ansible_TpE0Hc/ansible_module_azure_rm_networkinterface.py", line 408, in __init__
    required_if=required_if)
  File "/tmp/ansible_TpE0Hc/ansible_modlib.zip/ansible/module_utils/azure_rm_common.py", line 332, in __init__
  File "/tmp/ansible_TpE0Hc/ansible_module_azure_rm_networkinterface.py", line 516, in exec_module
    ) for ip_config in self.ip_configurations
  File "/tmp/ansible_TpE0Hc/ansible_module_azure_rm_networkinterface.py", line 546, in get_or_create_public_ip_address
    poller = self.network_client.public_ip_addresses.create_or_update(self.resource_group, name, params)
  File "/home/paolomargara/ansible/local/lib/python2.7/site-packages/azure/mgmt/network/v2017_06_01/operations/public_ip_addresses_operations.py", line 212, in create_or_update
    'publicIpAddressName': self._serialize.url("public_ip_address_name", public_ip_address_name, 'str'),
  File "/home/paolomargara/ansible/local/lib/python2.7/site-packages/msrest/serialization.py", line 488, in url
    data = self.validate(data, name, required=True, **kwargs)
  File "/home/paolomargara/ansible/local/lib/python2.7/site-packages/msrest/serialization.py", line 558, in validate
    raise ValidationError("required", name, True)
msrest.exceptions.ValidationError: Parameter 'public_ip_address_name' can not be None.

fatal: [localhost]: FAILED! => {
    "changed": false, 
    "failed": true, 
    "module_stderr": "Traceback (most recent call last):\n  File \"/tmp/ansible_TpE0Hc/ansible_module_azure_rm_networkinterface.py\", line 595, in <module>\n    main()\n  File \"/tmp/ansible_TpE0Hc/ansible_module_azure_rm_networkinterface.py\", line 591, in main\n    AzureRMNetworkInterface()\n  File \"/tmp/ansible_TpE0Hc/ansible_module_azure_rm_networkinterface.py\", line 408, in __init__\n    required_if=required_if)\n  File \"/tmp/ansible_TpE0Hc/ansible_modlib.zip/ansible/module_utils/azure_rm_common.py\", line 332, in __init__\n  File \"/tmp/ansible_TpE0Hc/ansible_module_azure_rm_networkinterface.py\", line 516, in exec_module\n    ) for ip_config in self.ip_configurations\n  File \"/tmp/ansible_TpE0Hc/ansible_module_azure_rm_networkinterface.py\", line 546, in get_or_create_public_ip_address\n    poller = self.network_client.public_ip_addresses.create_or_update(self.resource_group, name, params)\n  File \"/home/paolomargara/ansible/local/lib/python2.7/site-packages/azure/mgmt/network/v2017_06_01/operations/public_ip_addresses_operations.py\", line 212, in create_or_update\n    'publicIpAddressName': self._serialize.url(\"public_ip_address_name\", public_ip_address_name, 'str'),\n  File \"/home/paolomargara/ansible/local/lib/python2.7/site-packages/msrest/serialization.py\", line 488, in url\n    data = self.validate(data, name, required=True, **kwargs)\n  File \"/home/paolomargara/ansible/local/lib/python2.7/site-packages/msrest/serialization.py\", line 558, in validate\n    raise ValidationError(\"required\", name, True)\nmsrest.exceptions.ValidationError: Parameter 'public_ip_address_name' can not be None.\n", 
    "module_stdout": "", 
    "msg": "MODULE FAILURE", 
    "rc": 1
}
```


Discussion:
Files identified in the description:
* [lib/ansible/modules/cloud/azure/azure_rm_networkinterface.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_networkinterface.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @haroldwongms @nitzmahone @trstringer @xscript @yuwzho @zikalino
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
i will look into this issue by the end of the week

Resolving patch:
diff --git a/lib/ansible/modules/cloud/azure/azure_rm_networkinterface.py b/lib/ansible/modules/cloud/azure/azure_rm_networkinterface.py
--- a/lib/ansible/modules/cloud/azure/azure_rm_networkinterface.py
+++ b/lib/ansible/modules/cloud/azure/azure_rm_networkinterface.py
@@ -173,68 +173,68 @@
 EXAMPLES = '''
     - name: Create a network interface with minimal parameters
       azure_rm_networkinterface:
-            name: nic001
-            resource_group: Testing
-            virtual_network_name: vnet001
-            subnet_name: subnet001
-            ip_configurations:
-                name: ipconfig1
-                public_ip_address_name: publicip001
-                primary: True
+        name: nic001
+        resource_group: Testing
+        virtual_network_name: vnet001
+        subnet_name: subnet001
+        ip_configurations:
+          - name: ipconfig1
+            public_ip_address_name: publicip001
+            primary: True
 
     - name: Create a network interface with private IP address only (no Public IP)
       azure_rm_networkinterface:
-            name: nic001
-            resource_group: Testing
-            virtual_network_name: vnet001
-            subnet_name: subnet001
-            ip_configurations:
-                name: ipconfig1
-                primary: True
+        name: nic001
+        resource_group: Testing
+        virtual_network_name: vnet001
+        subnet_name: subnet001
+        ip_configurations:
+          - name: ipconfig1
+            primary: True
 
     - name: Create a network interface for use in a Windows host (opens RDP port) with custom RDP port
       azure_rm_networkinterface:
-            name: nic002
-            resource_group: Testing
-            virtual_network_name: vnet001
-            subnet_name: subnet001
-            os_type: Windows
-            rdp_port: 3399
-            ip_configurations:
-                name: ipconfig1
-                public_ip_address_name: publicip001
-                primary: True
+        name: nic002
+        resource_group: Testing
+        virtual_network_name: vnet001
+        subnet_name: subnet001
+        os_type: Windows
+        rdp_port: 3399
+        ip_configurations:
+          - name: ipconfig1
+            public_ip_address_name: publicip001
+            primary: True
 
     - name: Create a network interface using existing security group and public IP
       azure_rm_networkinterface:
-            name: nic003
-            resource_group: Testing
-            virtual_network_name: vnet001
-            subnet_name: subnet001
-            security_group_name: secgroup001
-            ip_configurations:
-                name: ipconfig1
-                public_ip_address_name: publicip001
-                primary: True
+        name: nic003
+        resource_group: Testing
+        virtual_network_name: vnet001
+        subnet_name: subnet001
+        security_group_name: secgroup001
+        ip_configurations:
+          - name: ipconfig1
+            public_ip_address_name: publicip001
+            primary: True
 
     - name: Create a network with mutilple ip configurations
       azure_rm_networkinterface:
-            name: nic004
-            resource_group: Testing
-            subnet_name: subnet001
-            virtual_network_name: vnet001
-            security_group_name: secgroup001
-            ip_configurations:
-                - name: ipconfig1
-                  public_ip_address_name: publicip001
-                  primary: True
-                - name: ipconfig2
+        name: nic004
+        resource_group: Testing
+        subnet_name: subnet001
+        virtual_network_name: vnet001
+        security_group_name: secgroup001
+        ip_configurations:
+          - name: ipconfig1
+            public_ip_address_name: publicip001
+            primary: True
+          - name: ipconfig2
 
     - name: Delete network interface
       azure_rm_networkinterface:
-            resource_group: Testing
-            name: nic003
-            state: absent
+        resource_group: Testing
+        name: nic003
+        state: absent
 '''
 
 RETURN = '''
@@ -536,6 +536,10 @@ def exec_module(self, **kwargs):
 
     def get_or_create_public_ip_address(self, ip_config):
         name = ip_config.get('public_ip_address_name')
+
+        if not (self.public_ip and name):
+            return None
+
         pip = self.get_public_ip_address(name)
         if not pip:
             params = self.network_models.PublicIPAddress(


Test patch:
diff --git a/test/integration/targets/azure_rm_networkinterface/tasks/main.yml b/test/integration/targets/azure_rm_networkinterface/tasks/main.yml
--- a/test/integration/targets/azure_rm_networkinterface/tasks/main.yml
+++ b/test/integration/targets/azure_rm_networkinterface/tasks/main.yml
@@ -134,6 +134,22 @@
       - not output.state.ip_configuration
       - output.state.ip_configurations | length == 2
 
+- name: IP configuration without public IP
+  azure_rm_networkinterface:
+      resource_group: "{{ resource_group }}"
+      name: testnic001noip
+      security_group: testnic001
+      virtual_network: "{{ vn.state.id }}"
+      subnet: testnic001
+      ip_configurations:
+        - name: ipconfig1
+          primary: True
+  register: output
+
+- assert:
+    that:
+      - output.state.ip_configurations[0].public_ip_address == None
+
 - name: Delete the NIC (check mode)
   azure_rm_networkinterface:
       resource_group: "{{ resource_group }}"