Repository: ansible/ansible

Issue:
Cannot assign NSG from other resource group when creating subnet
##### ISSUE TYPE
 - Feature Idea

##### COMPONENT NAME
azure_rm_subnet

##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes below -->
```
ansible 2.4.3.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/home/admindig/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/dist-packages/ansible
  executable location = /usr/bin/ansible
  python version = 2.7.12 (default, Dec  4 2017, 14:50:18) [GCC 5.4.0 20160609]
```

##### CONFIGURATION
ANSIBLE_NOCOWS(/etc/ansible/ansible.cfg) = True
CACHE_PLUGIN(/etc/ansible/ansible.cfg) = jsonfile
CACHE_PLUGIN_CONNECTION(/etc/ansible/ansible.cfg) = /etc/ansible/fact_caching
CACHE_PLUGIN_TIMEOUT(/etc/ansible/ansible.cfg) = 86400
COMMAND_WARNINGS(/etc/ansible/ansible.cfg) = True
DEFAULT_FORKS(/etc/ansible/ansible.cfg) = 50
DEFAULT_GATHERING(/etc/ansible/ansible.cfg) = smart
DEFAULT_HOST_LIST(/etc/ansible/ansible.cfg) = [u'/etc/ansible/inventory.yaml']
DEFAULT_PRIVATE_KEY_FILE(/etc/ansible/ansible.cfg) = /home/username/.ssh/id_rsa
DEFAULT_VAULT_PASSWORD_FILE(/etc/ansible/ansible.cfg) = /etc/ansible/.vault_pass

##### OS / ENVIRONMENT
N/A

##### SUMMARY
There is currently no way, when using azure_rm_subnet, to assign a network security group (NSG) that isn't in the same resource group as the subnet, though this is possible through the Azure GUI.

##### STEPS TO REPRODUCE
This below is my yaml currently being using to create a subnet.
The `resource_group` argument is valid for the subnet, the vnet and the NSG (network security group).
What I would like to see implemented would be a separate argument _a la_ `security_resource_group` or `nsg_resource_group`, that would be used to specify the NSG's resource group, while not impacting where the vnet is gathered from, and where the subnet is created/gathered.

<!--- Paste example playbooks or commands between quotes below -->
```yaml
- name: Create new subnet if not existent
  azure_rm_subnet:
    address_prefix_cidr: "{{ vnet_cidr_prefix }}"
    name: "{{ subnet_name }}"
    profile: "{{ azure_profile }}"
    resource_group: "{{ resource_group }}"
    state: present
    virtual_network_name: "{{ vnet_name }}"
```



Discussion:
Files identified in the description:
* [lib/ansible/modules/cloud/azure/azure_rm_subnet.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_subnet.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 --->
@alexpilon666 I think this should be a feature request for NSG module rather than subnet module. My proposal is add a `subnets` parameter filed in the NSG module to specify the Virtual Network name and subnet name. Like this:
```
azure_rm_securitygroup:
  name: test01
  resource_group: test01
  subnets:
  - resource_group: test02  # None means the same as security group
    virtual_network: test02
    subnet: test02
  - virtual_network test01
    subnet: test01
```

How do you think about it?
@yuwzho, sure! One way or the other, the result ends up being the same, so having it into the NSG or Subnet module doesn't really matter to me personally in the end, all depends on how the Ansible team wants to implement it hehe 
@alexpilon666 I would like to implement this feature if I have time before April

Resolving patch:
diff --git a/lib/ansible/modules/cloud/azure/azure_rm_subnet.py b/lib/ansible/modules/cloud/azure/azure_rm_subnet.py
--- a/lib/ansible/modules/cloud/azure/azure_rm_subnet.py
+++ b/lib/ansible/modules/cloud/azure/azure_rm_subnet.py
@@ -39,11 +39,14 @@
         required: true
         aliases:
             - address_prefix
-    security_group_name:
+    security_group:
         description:
-            - Name of an existing security group with which to associate the subnet.
+            - Existing security group with which to associate the subnet.
+            - It can be the security group name which is in the same resource group.
+            - It can be the resource Id.
+            - It can be a dict which contains C(name) and C(resource_group) of the security group.
         aliases:
-            - security_group
+            - security_group_name
     state:
         description:
             - Assert the state of the subnet. Use 'present' to create or update a subnet and
@@ -76,6 +79,16 @@
         resource_group: Testing
         address_prefix_cidr: "10.1.0.0/24"
 
+    - name: Create a subnet refer nsg from other resource group
+      azure_rm_subnet:
+        name: foobar
+        virtual_network_name: My_Virtual_Network
+        resource_group: Testing
+        address_prefix_cidr: "10.1.0.0/16"
+        security_group:
+          name: secgroupfoo
+          resource_group: Testing1
+
     - name: Delete a subnet
       azure_rm_subnet:
         name: foobar
@@ -119,7 +132,7 @@
           example: "Succeeded"
 '''  # NOQA
 
-from ansible.module_utils.azure_rm_common import AzureRMModuleBase, CIDR_PATTERN, azure_id_to_dict
+from ansible.module_utils.azure_rm_common import AzureRMModuleBase, CIDR_PATTERN, azure_id_to_dict, format_resource_id
 
 try:
     from msrestazure.azure_exceptions import CloudError
@@ -153,7 +166,7 @@ def __init__(self):
             state=dict(type='str', default='present', choices=['present', 'absent']),
             virtual_network_name=dict(type='str', required=True, aliases=['virtual_network']),
             address_prefix_cidr=dict(type='str', aliases=['address_prefix']),
-            security_group_name=dict(type='str', aliases=['security_group']),
+            security_group=dict(type='raw', aliases=['security_group_name'])
         )
 
         required_if = [
@@ -170,7 +183,7 @@ def __init__(self):
         self.state = None
         self.virtual_network_name = None
         self.address_prefix_cidr = None
-        self.security_group_name = None
+        self.security_group = None
 
         super(AzureRMSubnet, self).__init__(self.module_arg_spec,
                                             supports_check_mode=True,
@@ -187,8 +200,8 @@ def exec_module(self, **kwargs):
         if self.state == 'present' and not CIDR_PATTERN.match(self.address_prefix_cidr):
             self.fail("Invalid address_prefix_cidr value {0}".format(self.address_prefix_cidr))
 
-        if self.security_group_name:
-            nsg = self.get_security_group(self.security_group_name)
+        if self.security_group:
+            nsg = self.parse_nsg()
 
         results = dict()
         changed = False
@@ -208,12 +221,12 @@ def exec_module(self, **kwargs):
                         changed = True
                         results['address_prefix'] = self.address_prefix_cidr
 
-                if self.security_group_name:
-                    if results['network_security_group'].get('id') != nsg.id:
+                if nsg:
+                    if results['network_security_group'].get('id') != nsg.get('id'):
                         self.log("CHANGED: subnet {0} network security group".format(self.name))
                         changed = True
-                        results['network_security_group']['id'] = nsg.id
-                        results['network_security_group']['name'] = nsg.name
+                        results['network_security_group']['id'] = nsg.get('id')
+                        results['network_security_group']['name'] = nsg.get('name')
             elif self.state == 'absent':
                 changed = True
         except CloudError:
@@ -234,9 +247,7 @@ def exec_module(self, **kwargs):
                         address_prefix=self.address_prefix_cidr
                     )
                     if nsg:
-                        subnet.network_security_group = self.network_models.NetworkSecurityGroup(id=nsg.id,
-                                                                                                 location=nsg.location,
-                                                                                                 resource_guid=nsg.resource_guid)
+                        subnet.network_security_group = self.network_models.NetworkSecurityGroup(id=nsg.get('id'))
 
                 else:
                     # update subnet
@@ -245,10 +256,7 @@ def exec_module(self, **kwargs):
                         address_prefix=results['address_prefix']
                     )
                     if results['network_security_group'].get('id'):
-                        nsg = self.get_security_group(results['network_security_group']['name'])
-                        subnet.network_security_group = self.network_models.NetworkSecurityGroup(id=nsg.id,
-                                                                                                 location=nsg.location,
-                                                                                                 resource_guid=nsg.resource_guid)
+                        subnet.network_security_group = self.network_models.NetworkSecurityGroup(results['network_security_group'].get('id'))
 
                 self.results['state'] = self.create_or_update_subnet(subnet)
             elif self.state == 'absent' and changed:
@@ -284,14 +292,19 @@ def delete_subnet(self):
 
         return result
 
-    def get_security_group(self, name):
-        self.log("Fetching security group {0}".format(name))
-        nsg = None
-        try:
-            nsg = self.network_client.network_security_groups.get(self.resource_group, name)
-        except Exception as exc:
-            self.fail("Error: fetching network security group {0} - {1}.".format(name, str(exc)))
-        return nsg
+    def parse_nsg(self):
+        nsg = self.security_group
+        resource_group = self.resource_group
+        if isinstance(self.security_group, dict):
+            nsg = self.security_group.get('name')
+            resource_group = self.security_group.get('resource_group', self.resource_group)
+        id = format_resource_id(val=nsg,
+                                subscription_id=self.subscription_id,
+                                namespace='Microsoft.Network',
+                                types='networkSecurityGroups',
+                                resource_group=resource_group)
+        name = azure_id_to_dict(id).get('name')
+        return dict(id=id, name=name)
 
 
 def main():


Test patch:
diff --git a/test/integration/targets/azure_rm_subnet/tasks/main.yml b/test/integration/targets/azure_rm_subnet/tasks/main.yml
--- a/test/integration/targets/azure_rm_subnet/tasks/main.yml
+++ b/test/integration/targets/azure_rm_subnet/tasks/main.yml
@@ -75,6 +75,46 @@
 - assert:
     that: not output.changed
 
+- name: Create network security group in another resource group
+  azure_rm_securitygroup:
+    name: secgroupfoo
+    resource_group: "{{ resource_group_secondary }}"
+  register: nsg
+
+- name: Update the subnet
+  azure_rm_subnet:
+    name: foobar
+    virtual_network_name: My_Virtual_Network
+    resource_group: "{{ resource_group }}"
+    address_prefix_cidr: "10.1.0.0/16"
+    security_group:
+      name: secgroupfoo
+      resource_group: "{{ resource_group_secondary }}"
+    tags:
+       testing: testing
+       delete: on-fini
+  register: output
+
+- assert:
+    that: 
+    - output.changed
+    - output.state.network_security_group.id == nsg.state.id
+
+- name: Update the subnet (idempotent)
+  azure_rm_subnet:
+    name: foobar
+    virtual_network_name: My_Virtual_Network
+    resource_group: "{{ resource_group }}"
+    address_prefix_cidr: "10.1.0.0/16"
+    security_group: "{{ nsg.state.id }}"
+    tags:
+       testing: testing
+       delete: on-fini
+  register: output
+
+- assert:
+    that: not output.changed
+
 - name: Remove subnet
   azure_rm_subnet:
     state: absent