Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added vlan trunks on the server detail page #2105

Merged
merged 8 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 11 additions & 12 deletions .secrets.baseline
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"files": "^.secrets.baseline$",
"lines": null
},
"generated_at": "2023-09-27T14:21:34Z",
"generated_at": "2023-10-13T20:28:05Z",
"plugins_used": [
{
"name": "AWSKeyDetector"
Expand Down Expand Up @@ -238,7 +238,7 @@
"hashed_secret": "fb5f2f1b65d1f2bc130ce9d5729b38d12f2b444e",
"is_secret": false,
"is_verified": false,
"line_number": 259,
"line_number": 274,
"type": "Secret Keyword",
"verified_result": null
}
Expand Down Expand Up @@ -529,23 +529,22 @@
"verified_result": null
}
],
"tests/CLI/modules/securitygroup_tests.py": [
"tests/CLI/modules/hardware/hardware_basic_tests.py": [
{
"hashed_secret": "bc553d847e40dd6f3f63638f16f57b28ce1425cc",
"is_secret": false,
"hashed_secret": "6367c48dd193d56ea7b0baad25b19455e529f5ee",
"is_verified": false,
"line_number": 339,
"type": "Hex High Entropy String",
"line_number": 57,
"type": "Secret Keyword",
"verified_result": null
}
],
"tests/CLI/modules/server_tests.py": [
"tests/CLI/modules/securitygroup_tests.py": [
{
"hashed_secret": "6367c48dd193d56ea7b0baad25b19455e529f5ee",
"hashed_secret": "bc553d847e40dd6f3f63638f16f57b28ce1425cc",
"is_secret": false,
"is_verified": false,
"line_number": 57,
"type": "Secret Keyword",
"line_number": 339,
"type": "Hex High Entropy String",
"verified_result": null
}
],
Expand Down Expand Up @@ -594,7 +593,7 @@
"hashed_secret": "fb5f2f1b65d1f2bc130ce9d5729b38d12f2b444e",
"is_secret": false,
"is_verified": false,
"line_number": 737,
"line_number": 673,
"type": "Secret Keyword",
"verified_result": null
}
Expand Down
20 changes: 20 additions & 0 deletions SoftLayer/CLI/formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,22 @@ def __init__(self, columns, title=None, align=None):
self.align = align or {}
self.sortby = None
self.title = title
# Used to print a message if the table is empty
self.empty_message = None

def __bool__(self):
"""Useful for seeing if the table has any rows"""
return len(self.rows) > 0

def set_empty_message(self, message):
"""Sets the empty message for this table for env.fout

Set this message if you want to print a message instead of a table to the user
but still want the json output to print an empty list `[]`

:param message str: Message to print if the table has no rows
"""
self.empty_message = message

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

def prettytable(self, fmt='table', theme=None):
"""Returns a RICH table instance."""

# Used to print a message instead of a bad looking empty table
if not self and self.empty_message:
return self.empty_message
box_style = box.SQUARE
if fmt == 'raw':
box_style = None
Expand Down
29 changes: 24 additions & 5 deletions SoftLayer/CLI/hardware/detail.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def cli(env, identifier, passwords, price, components):
table.align['value'] = 'l'

hardware_id = helpers.resolve_id(hardware.resolve_ids, identifier, 'hardware')
result = hardware.get_hardware(hardware_id)
result = hardware.get_hardware_fast(hardware_id)
result = utils.NestedDict(result)
hard_drives = hardware.get_hard_drives(hardware_id)

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

vlan_table = formatting.Table(['type', 'number', 'id', 'name', 'netmask'])
vlan_table = formatting.Table(['Network', 'Number', 'Id', 'Name', 'Type'])
for vlan in result['networkVlans']:
vlan_table.add_row([vlan['networkSpace'], vlan['vlanNumber'],
vlan['id'], vlan['fullyQualifiedName'],
vlan['primarySubnets'][0]['netmask']])
vlan_table.add_row([
vlan.get('networkSpace'),
vlan.get('vlanNumber'),
vlan['id'],
vlan['fullyQualifiedName'],
'Primary'
])

# Shows any VLANS trunked/tagged on this server
for component in result.get('networkComponents', []):
# These are the Primary network components
if component.get('primaryIpAddress', False):
uplink = component.get('uplinkComponent', {})
for trunk in uplink.get('networkVlanTrunks', []):
trunk_vlan = trunk.get('networkVlan')
vlan_table.add_row([
trunk_vlan.get('networkSpace'),
trunk_vlan.get('vlanNumber'),
trunk_vlan.get('id'),
trunk_vlan.get('fullyQualifiedName'),
'Trunked'
])

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

Expand Down
60 changes: 60 additions & 0 deletions SoftLayer/CLI/hardware/vlan_add.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""Trunk a VLAN to this server."""
# :license: MIT, see LICENSE for more details.

import click

import SoftLayer
from SoftLayer.CLI import environment
from SoftLayer.CLI import exceptions
from SoftLayer.CLI import helpers


@click.command(cls=SoftLayer.CLI.command.SLCommand, )
@click.argument('hardware', nargs=1)
@click.argument('vlans', nargs=-1)
@environment.pass_env
def cli(env, hardware, vlans):
"""Trunk a VLAN to this server.

HARDWARE is the id of the server
VLANS is the ID, name, or number of the VLANs you want to add. Multiple vlans can be added at the same time.
It is recommended to use the vlan ID, especially if you have multiple vlans with the same name/number.
"""

if not vlans:
raise exceptions.ArgumentError("Error: Missing argument 'VLANS'.")
h_mgr = SoftLayer.HardwareManager(env.client)
n_mgr = SoftLayer.NetworkManager(env.client)
hw_id = helpers.resolve_id(h_mgr.resolve_ids, hardware, 'hardware')
# Enclosing in quotes is required for any input that has a space in it.
# "Public DAL10" for example needs to be sent to search as \"Public DAL10\"
sl_vlans = n_mgr.search_for_vlan(" ".join(f"\"{v}\"" for v in vlans))
if not sl_vlans:
raise exceptions.ArgumentError(f"No vlans found matching {' '.join(vlans)}")
add_vlans = parse_vlans(sl_vlans)
component_mask = "mask[id, name, port, macAddress, primaryIpAddress]"
# NEXT: Add nice output / exception handling
if len(add_vlans['public']) > 0:
components = h_mgr.get_network_components(hw_id, mask=component_mask, space='public')
for c in components:
if c.get('primaryIpAddress'):
h_mgr.trunk_vlan(c.get('id'), add_vlans['public'])
if len(add_vlans['private']) > 0:
components = h_mgr.get_network_components(hw_id, mask=component_mask, space='private')
for c in components:
if c.get('primaryIpAddress'):
h_mgr.trunk_vlan(c.get('id'), add_vlans['private'])


def parse_vlans(vlans):
"""returns a dictionary mapping for public / private vlans"""

pub_vlan = []
pri_vlan = []
for vlan in vlans:
print(f"{vlan.get('networkSpace')} | {vlan.get('id')} -> {vlan.get('vlanNumber')}")
if vlan.get('networkSpace') == "PUBLIC":
pub_vlan.append(vlan)
else:
pri_vlan.append(vlan)
return {"public": pub_vlan, "private": pri_vlan}
65 changes: 65 additions & 0 deletions SoftLayer/CLI/hardware/vlan_remove.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""Remove VLANs trunked to this server."""
# :license: MIT, see LICENSE for more details.

import click

import SoftLayer
from SoftLayer.CLI import environment
from SoftLayer.CLI import exceptions
from SoftLayer.CLI import helpers


@click.command(cls=SoftLayer.CLI.command.SLCommand, )
@click.argument('hardware', nargs=1)
@click.argument('vlans', nargs=-1)
@click.option('--all', 'all_vlans', is_flag=True, default=False, help="Remove ALL trunked vlans from this server.")
@environment.pass_env
def cli(env, hardware, vlans, all_vlans):
"""Remove VLANs trunked to this server.

HARDWARE is the id of the server
VLANS is the ID, name, or number of the VLANs you want to remove. Multiple vlans can be removed at the same time.
It is recommended to use the vlan ID, especially if you have multiple vlans with the same name/number.
"""
if not vlans and not all_vlans:
raise exceptions.ArgumentError("Error: Missing argument 'VLANS'.")
h_mgr = SoftLayer.HardwareManager(env.client)
n_mgr = SoftLayer.NetworkManager(env.client)
hw_id = helpers.resolve_id(h_mgr.resolve_ids, hardware, 'hardware')

if all_vlans:
h_mgr.clear_vlan(hw_id)
env.fout("Done.")
return

# Enclosing in quotes is required for any input that has a space in it.
# "Public DAL10" for example needs to be sent to search as \"Public DAL10\"
sl_vlans = n_mgr.search_for_vlan(" ".join(f"\"{v}\"" for v in vlans))
if not sl_vlans:
raise exceptions.ArgumentError(f"No vlans found matching {' '.join(vlans)}")
del_vlans = parse_vlans(sl_vlans)
component_mask = "mask[id, name, port, macAddress, primaryIpAddress]"
# NEXT: Add nice output / exception handling
if len(del_vlans['public']) > 0:
components = h_mgr.get_network_components(hw_id, mask=component_mask, space='public')
for c in components:
if c.get('primaryIpAddress'):
h_mgr.remove_vlan(c.get('id'), del_vlans['public'])
if len(del_vlans['private']) > 0:
components = h_mgr.get_network_components(hw_id, mask=component_mask, space='private')
for c in components:
if c.get('primaryIpAddress'):
h_mgr.remove_vlan(c.get('id'), del_vlans['private'])


def parse_vlans(vlans):
"""returns a dictionary mapping for public / private vlans"""

pub_vlan = []
pri_vlan = []
for vlan in vlans:
if vlan.get('networkSpace') == "PUBLIC":
pub_vlan.append(vlan)
else:
pri_vlan.append(vlan)
return {"public": pub_vlan, "private": pri_vlan}
35 changes: 35 additions & 0 deletions SoftLayer/CLI/hardware/vlan_trunkable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""List VLANs this server can be attached to."""
# :license: MIT, see LICENSE for more details.

import click

import SoftLayer
from SoftLayer.CLI import environment
from SoftLayer.CLI import formatting
from SoftLayer.CLI import helpers


@click.command(cls=SoftLayer.CLI.command.SLCommand, )
@click.argument('hardware')
@environment.pass_env
def cli(env, hardware):
"""List VLANs this server can be attached to."""

mgr = SoftLayer.HardwareManager(env.client)
hw_id = helpers.resolve_id(mgr.resolve_ids, hardware, 'hardware')
mask = (
"mask[id,primaryIpAddress,"
"networkVlansTrunkable[id,name,vlanNumber,fullyQualifiedName,networkSpace]]"
)
table = formatting.Table(["ID", "VLAN", "Name", "Space"])
table.set_empty_message("No trunkable vlans found.")
hw_components = env.client.call('SoftLayer_Hardware_Server', 'getNetworkComponents', id=hw_id, mask=mask)

for component in hw_components:
if component.get('primaryIpAddress'):
for vlan in component.get('networkVlansTrunkable', []):
table.add_row([
vlan.get('id'), vlan.get('fullyQualifiedName'), vlan.get('name'), vlan.get('networkSpace')
])

env.fout(table)
3 changes: 3 additions & 0 deletions SoftLayer/CLI/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,9 @@
('hardware:notification-add', 'SoftLayer.CLI.hardware.notification_add:cli'),
('hardware:notification-delete', 'SoftLayer.CLI.hardware.notification_delete:cli'),
('hardware:create-credential', 'SoftLayer.CLI.hardware.create_credential:cli'),
('hardware:vlan-trunkable', 'SoftLayer.CLI.hardware.vlan_trunkable:cli'),
('hardware:vlan-add', 'SoftLayer.CLI.hardware.vlan_add:cli'),
('hardware:vlan-remove', 'SoftLayer.CLI.hardware.vlan_remove:cli'),

('securitygroup', 'SoftLayer.CLI.securitygroup'),
('securitygroup:list', 'SoftLayer.CLI.securitygroup.list:cli'),
Expand Down
Loading