Skip to content

Commit 60b4f84

Browse files
Merge pull request #2105 from allmightyspiff/vlanTrunk
Added vlan trunks on the server detail page
2 parents d0a3960 + 96b36f9 commit 60b4f84

18 files changed

+847
-321
lines changed

Diff for: .secrets.baseline

+11-12
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"files": "^.secrets.baseline$",
44
"lines": null
55
},
6-
"generated_at": "2023-09-27T14:21:34Z",
6+
"generated_at": "2023-10-13T20:28:05Z",
77
"plugins_used": [
88
{
99
"name": "AWSKeyDetector"
@@ -238,7 +238,7 @@
238238
"hashed_secret": "fb5f2f1b65d1f2bc130ce9d5729b38d12f2b444e",
239239
"is_secret": false,
240240
"is_verified": false,
241-
"line_number": 259,
241+
"line_number": 274,
242242
"type": "Secret Keyword",
243243
"verified_result": null
244244
}
@@ -529,23 +529,22 @@
529529
"verified_result": null
530530
}
531531
],
532-
"tests/CLI/modules/securitygroup_tests.py": [
532+
"tests/CLI/modules/hardware/hardware_basic_tests.py": [
533533
{
534-
"hashed_secret": "bc553d847e40dd6f3f63638f16f57b28ce1425cc",
535-
"is_secret": false,
534+
"hashed_secret": "6367c48dd193d56ea7b0baad25b19455e529f5ee",
536535
"is_verified": false,
537-
"line_number": 339,
538-
"type": "Hex High Entropy String",
536+
"line_number": 57,
537+
"type": "Secret Keyword",
539538
"verified_result": null
540539
}
541540
],
542-
"tests/CLI/modules/server_tests.py": [
541+
"tests/CLI/modules/securitygroup_tests.py": [
543542
{
544-
"hashed_secret": "6367c48dd193d56ea7b0baad25b19455e529f5ee",
543+
"hashed_secret": "bc553d847e40dd6f3f63638f16f57b28ce1425cc",
545544
"is_secret": false,
546545
"is_verified": false,
547-
"line_number": 57,
548-
"type": "Secret Keyword",
546+
"line_number": 339,
547+
"type": "Hex High Entropy String",
549548
"verified_result": null
550549
}
551550
],
@@ -594,7 +593,7 @@
594593
"hashed_secret": "fb5f2f1b65d1f2bc130ce9d5729b38d12f2b444e",
595594
"is_secret": false,
596595
"is_verified": false,
597-
"line_number": 737,
596+
"line_number": 673,
598597
"type": "Secret Keyword",
599598
"verified_result": null
600599
}

Diff for: SoftLayer/CLI/formatting.py

+20
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,22 @@ def __init__(self, columns, title=None, align=None):
318318
self.align = align or {}
319319
self.sortby = None
320320
self.title = title
321+
# Used to print a message if the table is empty
322+
self.empty_message = None
323+
324+
def __bool__(self):
325+
"""Useful for seeing if the table has any rows"""
326+
return len(self.rows) > 0
327+
328+
def set_empty_message(self, message):
329+
"""Sets the empty message for this table for env.fout
330+
331+
Set this message if you want to print a message instead of a table to the user
332+
but still want the json output to print an empty list `[]`
333+
334+
:param message str: Message to print if the table has no rows
335+
"""
336+
self.empty_message = message
321337

322338
def add_row(self, row):
323339
"""Add a row to the table.
@@ -337,6 +353,10 @@ def to_python(self):
337353

338354
def prettytable(self, fmt='table', theme=None):
339355
"""Returns a RICH table instance."""
356+
357+
# Used to print a message instead of a bad looking empty table
358+
if not self and self.empty_message:
359+
return self.empty_message
340360
box_style = box.SQUARE
341361
if fmt == 'raw':
342362
box_style = None

Diff for: SoftLayer/CLI/hardware/detail.py

+24-5
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def cli(env, identifier, passwords, price, components):
2929
table.align['value'] = 'l'
3030

3131
hardware_id = helpers.resolve_id(hardware.resolve_ids, identifier, 'hardware')
32-
result = hardware.get_hardware(hardware_id)
32+
result = hardware.get_hardware_fast(hardware_id)
3333
result = utils.NestedDict(result)
3434
hard_drives = hardware.get_hard_drives(hardware_id)
3535

@@ -72,11 +72,30 @@ def cli(env, identifier, passwords, price, components):
7272
table.add_row(['last_transaction', last_transaction])
7373
table.add_row(['billing', 'Hourly' if result['hourlyBillingFlag'] else 'Monthly'])
7474

75-
vlan_table = formatting.Table(['type', 'number', 'id', 'name', 'netmask'])
75+
vlan_table = formatting.Table(['Network', 'Number', 'Id', 'Name', 'Type'])
7676
for vlan in result['networkVlans']:
77-
vlan_table.add_row([vlan['networkSpace'], vlan['vlanNumber'],
78-
vlan['id'], vlan['fullyQualifiedName'],
79-
vlan['primarySubnets'][0]['netmask']])
77+
vlan_table.add_row([
78+
vlan.get('networkSpace'),
79+
vlan.get('vlanNumber'),
80+
vlan['id'],
81+
vlan['fullyQualifiedName'],
82+
'Primary'
83+
])
84+
85+
# Shows any VLANS trunked/tagged on this server
86+
for component in result.get('networkComponents', []):
87+
# These are the Primary network components
88+
if component.get('primaryIpAddress', False):
89+
uplink = component.get('uplinkComponent', {})
90+
for trunk in uplink.get('networkVlanTrunks', []):
91+
trunk_vlan = trunk.get('networkVlan')
92+
vlan_table.add_row([
93+
trunk_vlan.get('networkSpace'),
94+
trunk_vlan.get('vlanNumber'),
95+
trunk_vlan.get('id'),
96+
trunk_vlan.get('fullyQualifiedName'),
97+
'Trunked'
98+
])
8099

81100
table.add_row(['vlans', vlan_table])
82101

Diff for: SoftLayer/CLI/hardware/vlan_add.py

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
"""Trunk a VLAN to this server."""
2+
# :license: MIT, see LICENSE for more details.
3+
4+
import click
5+
6+
import SoftLayer
7+
from SoftLayer.CLI import environment
8+
from SoftLayer.CLI import exceptions
9+
from SoftLayer.CLI import helpers
10+
11+
12+
@click.command(cls=SoftLayer.CLI.command.SLCommand, )
13+
@click.argument('hardware', nargs=1)
14+
@click.argument('vlans', nargs=-1)
15+
@environment.pass_env
16+
def cli(env, hardware, vlans):
17+
"""Trunk a VLAN to this server.
18+
19+
HARDWARE is the id of the server
20+
VLANS is the ID, name, or number of the VLANs you want to add. Multiple vlans can be added at the same time.
21+
It is recommended to use the vlan ID, especially if you have multiple vlans with the same name/number.
22+
"""
23+
24+
if not vlans:
25+
raise exceptions.ArgumentError("Error: Missing argument 'VLANS'.")
26+
h_mgr = SoftLayer.HardwareManager(env.client)
27+
n_mgr = SoftLayer.NetworkManager(env.client)
28+
hw_id = helpers.resolve_id(h_mgr.resolve_ids, hardware, 'hardware')
29+
# Enclosing in quotes is required for any input that has a space in it.
30+
# "Public DAL10" for example needs to be sent to search as \"Public DAL10\"
31+
sl_vlans = n_mgr.search_for_vlan(" ".join(f"\"{v}\"" for v in vlans))
32+
if not sl_vlans:
33+
raise exceptions.ArgumentError(f"No vlans found matching {' '.join(vlans)}")
34+
add_vlans = parse_vlans(sl_vlans)
35+
component_mask = "mask[id, name, port, macAddress, primaryIpAddress]"
36+
# NEXT: Add nice output / exception handling
37+
if len(add_vlans['public']) > 0:
38+
components = h_mgr.get_network_components(hw_id, mask=component_mask, space='public')
39+
for c in components:
40+
if c.get('primaryIpAddress'):
41+
h_mgr.trunk_vlan(c.get('id'), add_vlans['public'])
42+
if len(add_vlans['private']) > 0:
43+
components = h_mgr.get_network_components(hw_id, mask=component_mask, space='private')
44+
for c in components:
45+
if c.get('primaryIpAddress'):
46+
h_mgr.trunk_vlan(c.get('id'), add_vlans['private'])
47+
48+
49+
def parse_vlans(vlans):
50+
"""returns a dictionary mapping for public / private vlans"""
51+
52+
pub_vlan = []
53+
pri_vlan = []
54+
for vlan in vlans:
55+
print(f"{vlan.get('networkSpace')} | {vlan.get('id')} -> {vlan.get('vlanNumber')}")
56+
if vlan.get('networkSpace') == "PUBLIC":
57+
pub_vlan.append(vlan)
58+
else:
59+
pri_vlan.append(vlan)
60+
return {"public": pub_vlan, "private": pri_vlan}

Diff for: SoftLayer/CLI/hardware/vlan_remove.py

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
"""Remove VLANs trunked to this server."""
2+
# :license: MIT, see LICENSE for more details.
3+
4+
import click
5+
6+
import SoftLayer
7+
from SoftLayer.CLI import environment
8+
from SoftLayer.CLI import exceptions
9+
from SoftLayer.CLI import helpers
10+
11+
12+
@click.command(cls=SoftLayer.CLI.command.SLCommand, )
13+
@click.argument('hardware', nargs=1)
14+
@click.argument('vlans', nargs=-1)
15+
@click.option('--all', 'all_vlans', is_flag=True, default=False, help="Remove ALL trunked vlans from this server.")
16+
@environment.pass_env
17+
def cli(env, hardware, vlans, all_vlans):
18+
"""Remove VLANs trunked to this server.
19+
20+
HARDWARE is the id of the server
21+
VLANS is the ID, name, or number of the VLANs you want to remove. Multiple vlans can be removed at the same time.
22+
It is recommended to use the vlan ID, especially if you have multiple vlans with the same name/number.
23+
"""
24+
if not vlans and not all_vlans:
25+
raise exceptions.ArgumentError("Error: Missing argument 'VLANS'.")
26+
h_mgr = SoftLayer.HardwareManager(env.client)
27+
n_mgr = SoftLayer.NetworkManager(env.client)
28+
hw_id = helpers.resolve_id(h_mgr.resolve_ids, hardware, 'hardware')
29+
30+
if all_vlans:
31+
h_mgr.clear_vlan(hw_id)
32+
env.fout("Done.")
33+
return
34+
35+
# Enclosing in quotes is required for any input that has a space in it.
36+
# "Public DAL10" for example needs to be sent to search as \"Public DAL10\"
37+
sl_vlans = n_mgr.search_for_vlan(" ".join(f"\"{v}\"" for v in vlans))
38+
if not sl_vlans:
39+
raise exceptions.ArgumentError(f"No vlans found matching {' '.join(vlans)}")
40+
del_vlans = parse_vlans(sl_vlans)
41+
component_mask = "mask[id, name, port, macAddress, primaryIpAddress]"
42+
# NEXT: Add nice output / exception handling
43+
if len(del_vlans['public']) > 0:
44+
components = h_mgr.get_network_components(hw_id, mask=component_mask, space='public')
45+
for c in components:
46+
if c.get('primaryIpAddress'):
47+
h_mgr.remove_vlan(c.get('id'), del_vlans['public'])
48+
if len(del_vlans['private']) > 0:
49+
components = h_mgr.get_network_components(hw_id, mask=component_mask, space='private')
50+
for c in components:
51+
if c.get('primaryIpAddress'):
52+
h_mgr.remove_vlan(c.get('id'), del_vlans['private'])
53+
54+
55+
def parse_vlans(vlans):
56+
"""returns a dictionary mapping for public / private vlans"""
57+
58+
pub_vlan = []
59+
pri_vlan = []
60+
for vlan in vlans:
61+
if vlan.get('networkSpace') == "PUBLIC":
62+
pub_vlan.append(vlan)
63+
else:
64+
pri_vlan.append(vlan)
65+
return {"public": pub_vlan, "private": pri_vlan}

Diff for: SoftLayer/CLI/hardware/vlan_trunkable.py

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"""List VLANs this server can be attached to."""
2+
# :license: MIT, see LICENSE for more details.
3+
4+
import click
5+
6+
import SoftLayer
7+
from SoftLayer.CLI import environment
8+
from SoftLayer.CLI import formatting
9+
from SoftLayer.CLI import helpers
10+
11+
12+
@click.command(cls=SoftLayer.CLI.command.SLCommand, )
13+
@click.argument('hardware')
14+
@environment.pass_env
15+
def cli(env, hardware):
16+
"""List VLANs this server can be attached to."""
17+
18+
mgr = SoftLayer.HardwareManager(env.client)
19+
hw_id = helpers.resolve_id(mgr.resolve_ids, hardware, 'hardware')
20+
mask = (
21+
"mask[id,primaryIpAddress,"
22+
"networkVlansTrunkable[id,name,vlanNumber,fullyQualifiedName,networkSpace]]"
23+
)
24+
table = formatting.Table(["ID", "VLAN", "Name", "Space"])
25+
table.set_empty_message("No trunkable vlans found.")
26+
hw_components = env.client.call('SoftLayer_Hardware_Server', 'getNetworkComponents', id=hw_id, mask=mask)
27+
28+
for component in hw_components:
29+
if component.get('primaryIpAddress'):
30+
for vlan in component.get('networkVlansTrunkable', []):
31+
table.add_row([
32+
vlan.get('id'), vlan.get('fullyQualifiedName'), vlan.get('name'), vlan.get('networkSpace')
33+
])
34+
35+
env.fout(table)

Diff for: SoftLayer/CLI/routes.py

+3
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,9 @@
315315
('hardware:notification-add', 'SoftLayer.CLI.hardware.notification_add:cli'),
316316
('hardware:notification-delete', 'SoftLayer.CLI.hardware.notification_delete:cli'),
317317
('hardware:create-credential', 'SoftLayer.CLI.hardware.create_credential:cli'),
318+
('hardware:vlan-trunkable', 'SoftLayer.CLI.hardware.vlan_trunkable:cli'),
319+
('hardware:vlan-add', 'SoftLayer.CLI.hardware.vlan_add:cli'),
320+
('hardware:vlan-remove', 'SoftLayer.CLI.hardware.vlan_remove:cli'),
318321

319322
('securitygroup', 'SoftLayer.CLI.securitygroup'),
320323
('securitygroup:list', 'SoftLayer.CLI.securitygroup.list:cli'),

0 commit comments

Comments
 (0)