Repository: softlayer/softlayer-python
Pull request #1651 (2022-05-24 20:04:09)

Update `slcli firewall detail` to handle multi vlan firewalls
Issue: https://github.com/softlayer/softlayer-python/issues/1650
Observations: Command was updated to handle multi vlan firewalls and unit tests was added 

![image](https://user-images.githubusercontent.com/78806346/170122170-f93eb5d5-a12a-4407-82d3-ec67e3022252.png)
![image](https://user-images.githubusercontent.com/78806346/170122326-2cca9b89-3f9f-432a-8c5f-36acc221d53b.png)



Patch:
diff --git a/SoftLayer/CLI/firewall/detail.py b/SoftLayer/CLI/firewall/detail.py
index 4c2f998b..aa0b4fa6 100644
--- a/SoftLayer/CLI/firewall/detail.py
+++ b/SoftLayer/CLI/firewall/detail.py
@@ -12,31 +12,77 @@
 
 @click.command(cls=SoftLayer.CLI.command.SLCommand, )
 @click.argument('identifier')
+@click.option('--credentials', type=click.BOOL,
+              help="Display FortiGate username and FortiGate password to multi vlans.")
 @environment.pass_env
-def cli(env, identifier):
-    """Detail firewall."""
+def cli(env, identifier, credentials):
+    """Detail firewall.
+
+    EXAMPLES:
+
+        slcli firewall detail vs:12345
+
+        slcli firewall detail --credentials true multiVlan:456789
+    """
 
     mgr = SoftLayer.FirewallManager(env.client)
 
     firewall_type, firewall_id = firewall.parse_id(identifier)
-    _firewall = mgr.get_instance(firewall_id)
 
-    table = formatting.KeyValueTable(['name', 'value'])
-    table.align['name'] = 'r'
-    table.align['value'] = 'l'
+    if firewall_type in ('vs', 'server', 'vlan', 'multiVlan'):
+
+        if firewall_type == 'vlan':
+            _firewall = mgr.get_instance(firewall_id)
+
+            table = formatting.KeyValueTable(['name', 'value'])
+            table.align['name'] = 'r'
+            table.align['value'] = 'l'
+
+            table.add_row(['id', _firewall.get('id')])
+            table.add_row(['primaryIpAddress', _firewall.get('primaryIpAddress')])
+            table.add_row(['datacenter', utils.lookup(_firewall, 'datacenter', 'longName')])
+            table.add_row(['networkVlan', utils.lookup(_firewall, 'networkVlan', 'name')])
+            table.add_row(['networkVlaniD', utils.lookup(_firewall, 'networkVlan', 'id')])
+
+            rules = mgr.get_dedicated_fwl_rules(firewall_id)
+            table.add_row(['rules', get_rules_table(rules)])
+
+        if firewall_type == 'multiVlan':
+            _firewall = mgr.get_instance(firewall_id)
+
+            table = formatting.KeyValueTable(['name', 'value'])
+            table.align['name'] = 'r'
+            table.align['value'] = 'l'
+
+            table.add_row(['name', utils.lookup(_firewall, 'networkGateway', 'name')])
+            table.add_row(['datacenter', utils.lookup(_firewall, 'datacenter', 'longName')])
+            table.add_row(['public ip', utils.lookup(_firewall, 'networkGateway', 'publicIpAddress', 'ipAddress')])
+            table.add_row(['private ip', utils.lookup(_firewall, 'networkGateway', 'privateIpAddress', 'ipAddress')])
+            table.add_row(['public  ipv6', utils.lookup(_firewall, 'networkGateway', 'publicIpv6Address', 'ipAddress')])
+            table.add_row(['public vlan', utils.lookup(_firewall, 'networkGateway', 'publicVlan', 'vlanNumber')])
+            table.add_row(['private vlan', utils.lookup(_firewall, 'networkGateway', 'privateVlan', 'vlanNumber')])
+            table.add_row(['type', _firewall.get('firewallType')])
+
+            if credentials:
+                table.add_row(['fortiGate username', utils.lookup(_firewall, 'managementCredentials', 'username')])
+                table.add_row(['fortiGate password', utils.lookup(_firewall, 'managementCredentials', 'password')])
+
+            rules = mgr.get_dedicated_fwl_rules(firewall_id)
+            if len(rules) != 0:
+                table.add_row(['rules', get_rules_table(rules)])
+            else:
+                table.add_row(['rules', '-'])
+
+        if firewall_type == 'vs' or firewall_type == 'server':
+            rules = mgr.get_standard_fwl_rules(firewall_id)
+            table = get_rules_table(rules)
 
-    table.add_row(['id', _firewall.get('id')])
-    table.add_row(['primaryIpAddress', _firewall.get('primaryIpAddress')])
-    table.add_row(['datacenter', utils.lookup(_firewall, 'datacenter', 'longName')])
-    table.add_row(['networkVlan', utils.lookup(_firewall, 'networkVlan', 'name')])
-    table.add_row(['networkVlaniD', utils.lookup(_firewall, 'networkVlan', 'id')])
+        env.fout(table)
 
-    if firewall_type == 'vlan':
-        rules = mgr.get_dedicated_fwl_rules(firewall_id)
     else:
-        rules = mgr.get_standard_fwl_rules(firewall_id)
-    table.add_row(['rules', get_rules_table(rules)])
-    env.fout(table)
+        click.secho('Invalid firewall type %s: firewall type should be either vlan, multiVlan, vs or server.'
+                    % firewall_type, fg='red')
+        return
 
 
 def get_rules_table(rules):
diff --git a/SoftLayer/fixtures/SoftLayer_Network_Vlan_Firewall.py b/SoftLayer/fixtures/SoftLayer_Network_Vlan_Firewall.py
index c2f4134f..1c57523e 100644
--- a/SoftLayer/fixtures/SoftLayer_Network_Vlan_Firewall.py
+++ b/SoftLayer/fixtures/SoftLayer_Network_Vlan_Firewall.py
@@ -49,6 +49,133 @@
             }
         ]
     },
+    "firewallType": "fortigate-security-appliance-10gb",
+    "managementCredentials": {
+        "createDate": "2022-05-17T13:59:17-06:00",
+        "id": 74604882,
+        "modifyDate": "2022-05-17T13:59:17-06:00",
+        "password": "test1234",
+        "port": 23,
+        "softwareId": 67804284,
+        "username": "myusername"
+    },
+    "networkGateway": {
+        "accountId": 307608,
+        "groupNumber": 1,
+        "id": 615448,
+        "name": "testFirewall",
+        "networkSpace": "BOTH",
+        "privateIpAddressId": 188996652,
+        "privateVlanId": 3228724,
+        "publicIpAddressId": 188996794,
+        "publicIpv6AddressId": 188996808,
+        "publicVlanId": 3228726,
+        "statusId": 1,
+        "insideVlans": [],
+        "members": [
+            {
+                "hardwareId": 3222842,
+                "id": 687820,
+                "networkGatewayId": 615448,
+                "priority": 254,
+                "networkGateway": None
+            }
+        ],
+        "privateIpAddress": {
+            "id": 188996652,
+            "ipAddress": "10.37.115.70",
+            "isBroadcast": False,
+            "isGateway": False,
+            "isNetwork": False,
+            "isReserved": True,
+            "subnetId": 2552734,
+            "subnet": {
+                "broadcastAddress": "10.37.115.127",
+                "cidr": 26,
+                "gateway": "10.37.115.65",
+                "id": 2552734,
+                "isCustomerOwned": False,
+                "isCustomerRoutable": False,
+                "modifyDate": "2022-05-17T13:59:16-06:00",
+                "netmask": "255.255.255.192",
+                "networkIdentifier": "10.37.115.64",
+                "networkVlanId": 3228724,
+                "sortOrder": "1",
+                "subnetType": "ADDITIONAL_PRIMARY",
+                "totalIpAddresses": "64",
+                "usableIpAddressCount": "61",
+                "version": 4
+            }
+        },
+        "privateVlan": {
+            "accountId": 307608,
+            "fullyQualifiedName": "dal13.bcr03.1330",
+            "id": 3228724,
+            "modifyDate": "2022-05-17T14:01:14-06:00",
+            "primarySubnetId": 2625456,
+            "vlanNumber": 1330
+        },
+        "publicIpAddress": {
+            "id": 188996794,
+            "ipAddress": "67.228.206.245",
+            "isBroadcast": False,
+            "isGateway": False,
+            "isNetwork": False,
+            "isReserved": True,
+            "subnetId": 66444,
+            "subnet": {
+                "broadcastAddress": "67.228.206.247",
+                "cidr": 29,
+                "gateway": "67.228.206.241",
+                "id": 66444,
+                "isCustomerOwned": False,
+                "isCustomerRoutable": False,
+                "modifyDate": "2022-05-17T13:59:16-06:00",
+                "netmask": "255.255.255.248",
+                "networkIdentifier": "67.228.206.240",
+                "networkVlanId": 3228726,
+                "sortOrder": "1",
+                "subnetType": "ADDITIONAL_PRIMARY",
+                "totalIpAddresses": "8",
+                "usableIpAddressCount": "5",
+                "version": 4
+            }
+        },
+        "publicIpv6Address": {
+            "id": 188996808,
+            "ipAddress": "2607:f0d0:2703:0039:0000:0000:0000:0004",
+            "isBroadcast": False,
+            "isGateway": False,
+            "isNetwork": False,
+            "isReserved": True,
+            "subnetId": 2547678,
+            "subnet": {
+                "broadcastAddress": "",
+                "cidr": 64,
+                "gateway": "2607:f0d0:2703:0039:0000:0000:0000:0001",
+                "id": 2547678,
+                "isCustomerOwned": False,
+                "isCustomerRoutable": False,
+                "modifyDate": "2022-05-17T13:59:16-06:00",
+                "netmask": "ffff:ffff:ffff:ffff:0000:0000:0000:0000",
+                "networkIdentifier": "2607:f0d0:2703:0039:0000:0000:0000:0000",
+                "networkVlanId": 3228726,
+                "sortOrder": "4",
+                "subnetType": "PRIMARY_6",
+                "totalIpAddresses": "18446744073709551616",
+                "usableIpAddressCount": "18446744073709551614",
+                "version": 6
+            }
+        },
+        "publicVlan": {
+            "accountId": 307608,
+            "fullyQualifiedName": "dal13.fcr03.1255",
+            "id": 3228726,
+            "modifyDate": "2022-05-17T14:00:42-06:00",
+            "primarySubnetId": 2623338,
+            "vlanNumber": 1255
+        }
+    },
     "rules": [
         {'destinationIpAddress': 'any on server',
          'protocol': 'tcp',
diff --git a/SoftLayer/managers/firewall.py b/SoftLayer/managers/firewall.py
index 44eef25a..3c8c496a 100644
--- a/SoftLayer/managers/firewall.py
+++ b/SoftLayer/managers/firewall.py
@@ -297,7 +297,8 @@ def get_instance(self, firewall_id, mask=None):
         :param integer firewall_id: the instance ID of the standard firewall
         """
         if not mask:
-            mask = 'mask[datacenter,networkVlan]'
+            mask = 'mask[firewallType,networkGateway[insideVlans,members,privateIpAddress,publicIpAddress,' \
+                   'publicIpv6Address,privateVlan,publicVlan],datacenter,managementCredentials,networkVlan]'
 
         svc = self.client['Network_Vlan_Firewall']
 


Test patch:
diff --git a/tests/CLI/modules/firewall_tests.py b/tests/CLI/modules/firewall_tests.py
index f248b16f..28e53a89 100644
--- a/tests/CLI/modules/firewall_tests.py
+++ b/tests/CLI/modules/firewall_tests.py
@@ -55,7 +55,7 @@ def test_add_server(self, confirm_mock):
         self.assert_no_fail(result)
         self.assertIn("Firewall is being created!", result.output)
 
-    def test_detail(self):
+    def test_detail_vlan_firewall(self):
         result = self.run_command(['firewall', 'detail', 'vlan:1234'])
         self.assert_no_fail(result)
         json_result = json.loads(result.output)
@@ -88,6 +88,76 @@ def test_detail(self):
                                      'src_ip': '0.0.0.0',
                                      'src_mask': '0.0.0.0'}]})
 
+    def test_detail_multi_vlan_firewall(self):
+        result = self.run_command(['firewall', 'detail', 'multiVlan:1234', '--credentials', 'true'])
+        self.assert_no_fail(result)
+        json_result = json.loads(result.output)
+        self.assertEqual(json_result['rules'][0]['action'], 'permit')
+        self.assertEqual(json.loads(result.output),
+                         {'name': 'testFirewall',
+                          'datacenter': 'Amsterdam 1',
+                          'public ip': '67.228.206.245',
+                          'private ip': '10.37.115.70',
+                          'public  ipv6': '2607:f0d0:2703:0039:0000:0000:0000:0004',
+                          'public vlan': 1255,
+                          'private vlan': 1330,
+                          'type': 'fortigate-security-appliance-10gb',
+                          'fortiGate username': 'myusername',
+                          'fortiGate password': 'test1234',
+                          'rules': [{'#': 1,
+                                     'action': 'permit',
+                                     'dest': 'any on server:80-80',
+                                     'dest_mask': '255.255.255.255',
+                                     'protocol': 'tcp',
+                                     'src_ip': '0.0.0.0',
+                                     'src_mask': '0.0.0.0'},
+                                    {'#': 2,
+                                     'action': 'permit',
+                                     'dest': 'any on server:1-65535',
+                                     'dest_mask': '255.255.255.255',
+                                     'protocol': 'tmp',
+                                     'src_ip': '193.212.1.10',
+                                     'src_mask': '255.255.255.255'},
+                                    {'#': 3,
+                                     'action': 'permit',
+                                     'dest': 'any on server:80-800',
+                                     'dest_mask': '255.255.255.255',
+                                     'protocol': 'tcp',
+                                     'src_ip': '0.0.0.0',
+                                     'src_mask': '0.0.0.0'}]})
+
+    def test_detail_vs_firewall(self):
+        result = self.run_command(['firewall', 'detail', 'vs:1234'])
+        self.assert_no_fail(result)
+        self.assertEqual(json.loads(result.output),
+                         [{'#': 1,
+                           'action': 'permit',
+                           'dest': 'any on server:80-80',
+                           'dest_mask': '255.255.255.255',
+                           'protocol': 'tcp',
+                           'src_ip': '0.0.0.0',
+                           'src_mask': '0.0.0.0'},
+                          {'#': 2,
+                           'action': 'permit',
+                           'dest': 'any on server:1-65535',
+                           'dest_mask': '255.255.255.255',
+                           'protocol': 'tcp',
+                           'src_ip': '193.212.1.10',
+                           'src_mask': '255.255.255.255'},
+                          {'#': 3,
+                           'action': 'permit',
+                           'dest': 'any on server:80-800',
+                           'dest_mask': '255.255.255.255',
+                           'protocol': 'tcp',
+                           'src_ip': '0.0.0.0',
+                           'src_mask': '0.0.0.0'}])
+
+    def test_detail_fails(self):
+        result = self.run_command(['firewall', 'detail', 'abc:1234'])
+        self.assert_no_fail(result)
+        expected_output = 'Invalid firewall type abc: firewall type should be either vlan, multiVlan, vs or server.\n'
+        self.assertEqual(result.output, expected_output)
+
     @mock.patch('SoftLayer.CLI.formatting.confirm')
     def test_cancel_firewall(self, confirm_mock):
         confirm_mock.return_value = True