Repository: ansible/ansible

Issue:
azure_rm_securitygroup doesn't show idempotent behavior 
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
`azure_rm_securitygroup`

##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
$ ansible --version
ansible 2.3.1.0
  config file = /Users/dmitry/foo/bar/ansible.cfg
  configured module search path = Default w/o overrides
  python version = 2.7.13 (default, Apr  4 2017, 08:47:57) [GCC 4.2.1 Compatible Apple LLVM 8.1.0 (clang-802.0.38)]

$ pip freeze | grep azure
azure==2.0.0rc5
azure-batch==0.30.0rc5
azure-common==1.1.4
azure-datalake-store==0.0.12
azure-graphrbac==0.30.0rc5
azure-keyvault==0.3.5
azure-mgmt==0.30.0rc5
azure-mgmt-authorization==0.30.0rc5
azure-mgmt-batch==0.30.0rc5
azure-mgmt-cdn==0.30.0rc5
azure-mgmt-cognitiveservices==0.30.0rc5
azure-mgmt-commerce==0.30.0rc5
azure-mgmt-compute==0.30.0rc5
azure-mgmt-containerregistry==0.2.1
azure-mgmt-datalake-analytics==0.1.6
azure-mgmt-datalake-nspkg==2.0.0
azure-mgmt-datalake-store==0.1.6
azure-mgmt-devtestlabs==2.0.0
azure-mgmt-dns==1.0.1
azure-mgmt-documentdb==0.1.3
azure-mgmt-iothub==0.2.2
azure-mgmt-keyvault==0.30.0rc5
azure-mgmt-logic==0.30.0rc5
azure-mgmt-monitor==0.2.1
azure-mgmt-network==0.30.0rc5
azure-mgmt-notificationhubs==0.30.0rc5
azure-mgmt-nspkg==2.0.0
azure-mgmt-powerbiembedded==0.30.0rc5
azure-mgmt-rdbms==0.1.0
azure-mgmt-redis==0.30.0rc5
azure-mgmt-resource==0.30.0rc5
azure-mgmt-scheduler==0.30.0rc5
azure-mgmt-sql==0.5.3
azure-mgmt-storage==0.30.0rc5
azure-mgmt-trafficmanager==0.30.0
azure-mgmt-web==0.30.0rc5
azure-nspkg==2.0.0
azure-servicebus==0.20.2
azure-servicefabric==5.6.130
azure-servicemanagement-legacy==0.20.3
azure-storage==0.32.0
msrestazure==0.4.11
```

##### CONFIGURATION
<!---

-->
```
[defaults]
ansible_managed = This file is managed by Ansible. Any changes will be overwritten.
forks = 100
hash_behaviour = replace
retry_files_save_path = ~/.ansible
allow_world_readable_tmpfiles = True
roles_path = ./roles:./roles/foo.bar
bin_ansible_callbacks = False

[ssh_connection]
pipelining = True
ssh_args = -o ControlMaster=auto -o ControlPersist=60m -o StrictHostKeyChecking=no
scp_if_ssh = True
```

##### OS / ENVIRONMENT
MacOS Sierra 10.12.15 -> Azure subscription via subscription_id, tennant_id, client_id, secret.

`Darwin Dmitrys-MBP.local 16.6.0 Darwin Kernel Version 16.6.0: Fri Apr 14 16:21:16 PDT 2017; root:xnu-3789.60.24~6/RELEASE_X86_64 x86_64`

##### SUMMARY
Running a simple play with `azure_rm_securitygroup` doesn't seem to be idempotent.
Task shows `changed: true` over and over even if no changes were made to the task.

##### STEPS TO REPRODUCE
```bash
ansible-playbook -i env/foo env/foo.yml  -vvv
```
<!--- Paste example playbooks or commands between quotes below -->
```yaml
- name: Create Security Group
  hosts: localhost
  connection: local
  gather_facts: no
  tasks:
    - name: "Create security group"
      azure_rm_securitygroup:
        resource_group: rg-foo
        name: foobar
        state: present

        rules:
          - name: SSH
            description: Allow SSH
            protocol: Tcp      
            destination_port_range: 22       
            access: Allow
            priority: 100
            direction: Inbound         
```


##### EXPECTED RESULTS
```
ok: [localhost]
```

##### ACTUAL RESULTS

```
TASK [Create security group] *******************************************************************************************************************************
task path: /Users/dmitry/dev/foo/bar/playbooks/activity.yml:20
Using module file /usr/local/lib/python2.7/site-packages/ansible/modules/cloud/azure/azure_rm_securitygroup.py
<localhost> ESTABLISH LOCAL CONNECTION FOR USER: dmitry
<localhost> EXEC /bin/sh -c '/usr/bin/env python && sleep 0'
changed: [localhost] => {
    "changed": true,
    "invocation": {
        "module_args": {
            "ad_user": null,
            "append_tags": true,
            "client_id": null,
            "default_rules": null,
            "location": null,
            "name": "foobar",
            "password": null,
            "profile": null,
            "purge_default_rules": false,
            "purge_rules": false,
            "resource_group": "rg-foo",
            "rules": [
                {
                    "access": "Allow",
                    "description": "Allow SSH",
                    "destination_address_prefix": "*",
                    "destination_port_range": 22,
                    "direction": "Inbound",
                    "name": "SSH",
                    "priority": 100,
                    "protocol": "Tcp",
                    "source_address_prefix": "*",
                    "source_port_range": "*"
                }
            ],
            "secret": null,
            "state": "present",
            "subscription_id": null,
            "tags": null,
            "tenant": null
        }
    },
    "state": {
        "default_rules": [
            {
                "access": "Allow",
                "description": "Allow inbound traffic from all VMs in VNET",
                "destination_address_prefix": "VirtualNetwork",
                "destination_port_range": "*",
                "direction": "Inbound",
                "etag": "W/\"<scrubbed>\"",
                "id": "/subscriptions/<scrubbed>/resourceGroups/rg-foo/providers/Microsoft.Network/networkSecurityGroups/foobar/defaultSecurityRules/AllowVnetInBound",
                "name": "AllowVnetInBound",
                "priority": 65000,
                "protocol": "*",
                "provisioning_state": "Succeeded",
                "source_address_prefix": "VirtualNetwork",
                "source_port_range": "*"
            },
            {
                "access": "Allow",
                "description": "Allow inbound traffic from azure load balancer",
                "destination_address_prefix": "*",
                "destination_port_range": "*",
                "direction": "Inbound",
                "etag": "W/\"<scrubbed>\"",
                "id": "/subscriptions/<scrubbed>/resourceGroups/rg-foo/providers/Microsoft.Network/networkSecurityGroups/foobar/defaultSecurityRules/AllowAzureLoadBalancerInBound",
                "name": "AllowAzureLoadBalancerInBound",
                "priority": 65001,
                "protocol": "*",
                "provisioning_state": "Succeeded",
                "source_address_prefix": "AzureLoadBalancer",
                "source_port_range": "*"
            },
            {
                "access": "Deny",
                "description": "Deny all inbound traffic",
                "destination_address_prefix": "*",
                "destination_port_range": "*",
                "direction": "Inbound",
                "etag": "W/\"<scrubbed>\"",
                "id": "/subscriptions/<scrubbed>/resourceGroups/rg-foo/providers/Microsoft.Network/networkSecurityGroups/foobar/defaultSecurityRules/DenyAllInBound",
                "name": "DenyAllInBound",
                "priority": 65500,
                "protocol": "*",
                "provisioning_state": "Succeeded",
                "source_address_prefix": "*",
                "source_port_range": "*"
            },
            {
                "access": "Allow",
                "description": "Allow outbound traffic from all VMs to all VMs in VNET",
                "destination_address_prefix": "VirtualNetwork",
                "destination_port_range": "*",
                "direction": "Outbound",
                "etag": "W/\"<scrubbed>\"",
                "id": "/subscriptions/<scrubbed>/resourceGroups/rg-foo/providers/Microsoft.Network/networkSecurityGroups/foobar/defaultSecurityRules/AllowVnetOutBound",
                "name": "AllowVnetOutBound",
                "priority": 65000,
                "protocol": "*",
                "provisioning_state": "Succeeded",
                "source_address_prefix": "VirtualNetwork",
                "source_port_range": "*"
            },
            {
                "access": "Allow",
                "description": "Allow outbound traffic from all VMs to Internet",
                "destination_address_prefix": "Internet",
                "destination_port_range": "*",
                "direction": "Outbound",
                "etag": "W/\"<scrubbed>\"",
                "id": "/subscriptions/<scrubbed>/resourceGroups/rg-foo/providers/Microsoft.Network/networkSecurityGroups/foobar/defaultSecurityRules/AllowInternetOutBound",
                "name": "AllowInternetOutBound",
                "priority": 65001,
                "protocol": "*",
                "provisioning_state": "Succeeded",
                "source_address_prefix": "*",
                "source_port_range": "*"
            },
            {
                "access": "Deny",
                "description": "Deny all outbound traffic",
                "destination_address_prefix": "*",
                "destination_port_range": "*",
                "direction": "Outbound",
                "etag": "W/\"<scrubbed>\"",
                "id": "/subscriptions/<scrubbed>/resourceGroups/rg-foo/providers/Microsoft.Network/networkSecurityGroups/foobar/defaultSecurityRules/DenyAllOutBound",
                "name": "DenyAllOutBound",
                "priority": 65500,
                "protocol": "*",
                "provisioning_state": "Succeeded",
                "source_address_prefix": "*",
                "source_port_range": "*"
            }
        ],
        "id": "/subscriptions/<scrubbed>/resourceGroups/rg-foo/providers/Microsoft.Network/networkSecurityGroups/foobar",
        "location": "westus2",
        "name": "foobar",
        "network_interfaces": [],
        "rules": [
            {
                "access": "Allow",
                "description": "Allow SSH",
                "destination_address_prefix": "*",
                "destination_port_range": "22",
                "direction": "Inbound",
                "etag": "W/\"<scrubbed>\"",
                "id": "/subscriptions/<scrubbed>/resourceGroups/rg-foo/providers/Microsoft.Network/networkSecurityGroups/foobar/securityRules/SSH",
                "name": "SSH",
                "priority": 100,
                "protocol": "Tcp",
                "provisioning_state": "Succeeded",
                "source_address_prefix": "*",
                "source_port_range": "*"
            }
        ],
        "subnets": [],
        "tags": {},
        "type": "Microsoft.Network/networkSecurityGroups"
    }
}
```

azure_rm_securitygroup doesn't show idempotent behavior 
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
`azure_rm_securitygroup`

##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
$ ansible --version
ansible 2.3.1.0
  config file = /Users/dmitry/foo/bar/ansible.cfg
  configured module search path = Default w/o overrides
  python version = 2.7.13 (default, Apr  4 2017, 08:47:57) [GCC 4.2.1 Compatible Apple LLVM 8.1.0 (clang-802.0.38)]

$ pip freeze | grep azure
azure==2.0.0rc5
azure-batch==0.30.0rc5
azure-common==1.1.4
azure-datalake-store==0.0.12
azure-graphrbac==0.30.0rc5
azure-keyvault==0.3.5
azure-mgmt==0.30.0rc5
azure-mgmt-authorization==0.30.0rc5
azure-mgmt-batch==0.30.0rc5
azure-mgmt-cdn==0.30.0rc5
azure-mgmt-cognitiveservices==0.30.0rc5
azure-mgmt-commerce==0.30.0rc5
azure-mgmt-compute==0.30.0rc5
azure-mgmt-containerregistry==0.2.1
azure-mgmt-datalake-analytics==0.1.6
azure-mgmt-datalake-nspkg==2.0.0
azure-mgmt-datalake-store==0.1.6
azure-mgmt-devtestlabs==2.0.0
azure-mgmt-dns==1.0.1
azure-mgmt-documentdb==0.1.3
azure-mgmt-iothub==0.2.2
azure-mgmt-keyvault==0.30.0rc5
azure-mgmt-logic==0.30.0rc5
azure-mgmt-monitor==0.2.1
azure-mgmt-network==0.30.0rc5
azure-mgmt-notificationhubs==0.30.0rc5
azure-mgmt-nspkg==2.0.0
azure-mgmt-powerbiembedded==0.30.0rc5
azure-mgmt-rdbms==0.1.0
azure-mgmt-redis==0.30.0rc5
azure-mgmt-resource==0.30.0rc5
azure-mgmt-scheduler==0.30.0rc5
azure-mgmt-sql==0.5.3
azure-mgmt-storage==0.30.0rc5
azure-mgmt-trafficmanager==0.30.0
azure-mgmt-web==0.30.0rc5
azure-nspkg==2.0.0
azure-servicebus==0.20.2
azure-servicefabric==5.6.130
azure-servicemanagement-legacy==0.20.3
azure-storage==0.32.0
msrestazure==0.4.11
```

##### CONFIGURATION
<!---

-->
```
[defaults]
ansible_managed = This file is managed by Ansible. Any changes will be overwritten.
forks = 100
hash_behaviour = replace
retry_files_save_path = ~/.ansible
allow_world_readable_tmpfiles = True
roles_path = ./roles:./roles/foo.bar
bin_ansible_callbacks = False

[ssh_connection]
pipelining = True
ssh_args = -o ControlMaster=auto -o ControlPersist=60m -o StrictHostKeyChecking=no
scp_if_ssh = True
```

##### OS / ENVIRONMENT
MacOS Sierra 10.12.15 -> Azure subscription via subscription_id, tennant_id, client_id, secret.

`Darwin Dmitrys-MBP.local 16.6.0 Darwin Kernel Version 16.6.0: Fri Apr 14 16:21:16 PDT 2017; root:xnu-3789.60.24~6/RELEASE_X86_64 x86_64`

##### SUMMARY
Running a simple play with `azure_rm_securitygroup` doesn't seem to be idempotent.
Task shows `changed: true` over and over even if no changes were made to the task.

##### STEPS TO REPRODUCE
```bash
ansible-playbook -i env/foo env/foo.yml  -vvv
```
<!--- Paste example playbooks or commands between quotes below -->
```yaml
- name: Create Security Group
  hosts: localhost
  connection: local
  gather_facts: no
  tasks:
    - name: "Create security group"
      azure_rm_securitygroup:
        resource_group: rg-foo
        name: foobar
        state: present

        rules:
          - name: SSH
            description: Allow SSH
            protocol: Tcp      
            destination_port_range: 22       
            access: Allow
            priority: 100
            direction: Inbound         
```


##### EXPECTED RESULTS
```
ok: [localhost]
```

##### ACTUAL RESULTS

```
TASK [Create security group] *******************************************************************************************************************************
task path: /Users/dmitry/dev/foo/bar/playbooks/activity.yml:20
Using module file /usr/local/lib/python2.7/site-packages/ansible/modules/cloud/azure/azure_rm_securitygroup.py
<localhost> ESTABLISH LOCAL CONNECTION FOR USER: dmitry
<localhost> EXEC /bin/sh -c '/usr/bin/env python && sleep 0'
changed: [localhost] => {
    "changed": true,
    "invocation": {
        "module_args": {
            "ad_user": null,
            "append_tags": true,
            "client_id": null,
            "default_rules": null,
            "location": null,
            "name": "foobar",
            "password": null,
            "profile": null,
            "purge_default_rules": false,
            "purge_rules": false,
            "resource_group": "rg-foo",
            "rules": [
                {
                    "access": "Allow",
                    "description": "Allow SSH",
                    "destination_address_prefix": "*",
                    "destination_port_range": 22,
                    "direction": "Inbound",
                    "name": "SSH",
                    "priority": 100,
                    "protocol": "Tcp",
                    "source_address_prefix": "*",
                    "source_port_range": "*"
                }
            ],
            "secret": null,
            "state": "present",
            "subscription_id": null,
            "tags": null,
            "tenant": null
        }
    },
    "state": {
        "default_rules": [
            {
                "access": "Allow",
                "description": "Allow inbound traffic from all VMs in VNET",
                "destination_address_prefix": "VirtualNetwork",
                "destination_port_range": "*",
                "direction": "Inbound",
                "etag": "W/\"<scrubbed>\"",
                "id": "/subscriptions/<scrubbed>/resourceGroups/rg-foo/providers/Microsoft.Network/networkSecurityGroups/foobar/defaultSecurityRules/AllowVnetInBound",
                "name": "AllowVnetInBound",
                "priority": 65000,
                "protocol": "*",
                "provisioning_state": "Succeeded",
                "source_address_prefix": "VirtualNetwork",
                "source_port_range": "*"
            },
            {
                "access": "Allow",
                "description": "Allow inbound traffic from azure load balancer",
                "destination_address_prefix": "*",
                "destination_port_range": "*",
                "direction": "Inbound",
                "etag": "W/\"<scrubbed>\"",
                "id": "/subscriptions/<scrubbed>/resourceGroups/rg-foo/providers/Microsoft.Network/networkSecurityGroups/foobar/defaultSecurityRules/AllowAzureLoadBalancerInBound",
                "name": "AllowAzureLoadBalancerInBound",
                "priority": 65001,
                "protocol": "*",
                "provisioning_state": "Succeeded",
                "source_address_prefix": "AzureLoadBalancer",
                "source_port_range": "*"
            },
            {
                "access": "Deny",
                "description": "Deny all inbound traffic",
                "destination_address_prefix": "*",
                "destination_port_range": "*",
                "direction": "Inbound",
                "etag": "W/\"<scrubbed>\"",
                "id": "/subscriptions/<scrubbed>/resourceGroups/rg-foo/providers/Microsoft.Network/networkSecurityGroups/foobar/defaultSecurityRules/DenyAllInBound",
                "name": "DenyAllInBound",
                "priority": 65500,
                "protocol": "*",
                "provisioning_state": "Succeeded",
                "source_address_prefix": "*",
                "source_port_range": "*"
            },
            {
                "access": "Allow",
                "description": "Allow outbound traffic from all VMs to all VMs in VNET",
                "destination_address_prefix": "VirtualNetwork",
                "destination_port_range": "*",
                "direction": "Outbound",
                "etag": "W/\"<scrubbed>\"",
                "id": "/subscriptions/<scrubbed>/resourceGroups/rg-foo/providers/Microsoft.Network/networkSecurityGroups/foobar/defaultSecurityRules/AllowVnetOutBound",
                "name": "AllowVnetOutBound",
                "priority": 65000,
                "protocol": "*",
                "provisioning_state": "Succeeded",
                "source_address_prefix": "VirtualNetwork",
                "source_port_range": "*"
            },
            {
                "access": "Allow",
                "description": "Allow outbound traffic from all VMs to Internet",
                "destination_address_prefix": "Internet",
                "destination_port_range": "*",
                "direction": "Outbound",
                "etag": "W/\"<scrubbed>\"",
                "id": "/subscriptions/<scrubbed>/resourceGroups/rg-foo/providers/Microsoft.Network/networkSecurityGroups/foobar/defaultSecurityRules/AllowInternetOutBound",
                "name": "AllowInternetOutBound",
                "priority": 65001,
                "protocol": "*",
                "provisioning_state": "Succeeded",
                "source_address_prefix": "*",
                "source_port_range": "*"
            },
            {
                "access": "Deny",
                "description": "Deny all outbound traffic",
                "destination_address_prefix": "*",
                "destination_port_range": "*",
                "direction": "Outbound",
                "etag": "W/\"<scrubbed>\"",
                "id": "/subscriptions/<scrubbed>/resourceGroups/rg-foo/providers/Microsoft.Network/networkSecurityGroups/foobar/defaultSecurityRules/DenyAllOutBound",
                "name": "DenyAllOutBound",
                "priority": 65500,
                "protocol": "*",
                "provisioning_state": "Succeeded",
                "source_address_prefix": "*",
                "source_port_range": "*"
            }
        ],
        "id": "/subscriptions/<scrubbed>/resourceGroups/rg-foo/providers/Microsoft.Network/networkSecurityGroups/foobar",
        "location": "westus2",
        "name": "foobar",
        "network_interfaces": [],
        "rules": [
            {
                "access": "Allow",
                "description": "Allow SSH",
                "destination_address_prefix": "*",
                "destination_port_range": "22",
                "direction": "Inbound",
                "etag": "W/\"<scrubbed>\"",
                "id": "/subscriptions/<scrubbed>/resourceGroups/rg-foo/providers/Microsoft.Network/networkSecurityGroups/foobar/securityRules/SSH",
                "name": "SSH",
                "priority": 100,
                "protocol": "Tcp",
                "provisioning_state": "Succeeded",
                "source_address_prefix": "*",
                "source_port_range": "*"
            }
        ],
        "subnets": [],
        "tags": {},
        "type": "Microsoft.Network/networkSecurityGroups"
    }
}
```



Discussion:
cc @haroldwongms @tstringer @nitzmahone
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
I really would like someone to confirm issue / confirm user error for this one.
I have some suspicion that that could be related to azure sdk version freeze (pip has `2.0.0` released, which doesn't seem to be compatible with `2.0.0rc5`)
Thank you for raising this issue, I'll look into this.
cc @haroldwongms @tstringer @nitzmahone
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
I really would like someone to confirm issue / confirm user error for this one.
I have some suspicion that that could be related to azure sdk version freeze (pip has `2.0.0` released, which doesn't seem to be compatible with `2.0.0rc5`)
Thank you for raising this issue, I'll look into this.

Resolving patch:
diff --git a/lib/ansible/modules/cloud/azure/azure_rm_securitygroup.py b/lib/ansible/modules/cloud/azure/azure_rm_securitygroup.py
--- a/lib/ansible/modules/cloud/azure/azure_rm_securitygroup.py
+++ b/lib/ansible/modules/cloud/azure/azure_rm_securitygroup.py
@@ -416,12 +416,12 @@ def compare_rules(r, rule):
         if rule['protocol'] != r['protocol']:
             changed = True
             r['protocol'] = rule['protocol']
-        if rule['source_port_range'] != r['source_port_range']:
+        if str(rule['source_port_range']) != str(r['source_port_range']):
             changed = True
-            r['source_port_range'] = rule['source_port_range']
-        if rule['destination_port_range'] != r['destination_port_range']:
+            r['source_port_range'] = str(rule['source_port_range'])
+        if str(rule['destination_port_range']) != str(r['destination_port_range']):
             changed = True
-            r['destination_port_range'] = rule['destination_port_range']
+            r['destination_port_range'] = str(rule['destination_port_range'])
         if rule['access'] != r['access']:
             changed = True
             r['access'] = rule['access']


Test patch: