Skip to content

Commit 0c21f70

Browse files
Merge pull request #1120 from allmightyspiff/billing
Invoice and event CLI
2 parents 0790844 + 843f531 commit 0c21f70

20 files changed

+727
-25
lines changed

SoftLayer/CLI/account/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Account commands"""
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
"""Details of a specific event, and ability to acknowledge event."""
2+
# :license: MIT, see LICENSE for more details.
3+
4+
import click
5+
6+
from SoftLayer.CLI import environment
7+
from SoftLayer.CLI import formatting
8+
from SoftLayer.managers.account import AccountManager as AccountManager
9+
from SoftLayer import utils
10+
11+
12+
@click.command()
13+
@click.argument('identifier')
14+
@click.option('--ack', is_flag=True, default=False,
15+
help="Acknowledge Event. Doing so will turn off the popup in the control portal")
16+
@environment.pass_env
17+
def cli(env, identifier, ack):
18+
"""Details of a specific event, and ability to acknowledge event."""
19+
20+
# Print a list of all on going maintenance
21+
manager = AccountManager(env.client)
22+
event = manager.get_event(identifier)
23+
24+
if ack:
25+
manager.ack_event(identifier)
26+
27+
env.fout(basic_event_table(event))
28+
env.fout(impacted_table(event))
29+
env.fout(update_table(event))
30+
31+
32+
def basic_event_table(event):
33+
"""Formats a basic event table"""
34+
table = formatting.Table(["Id", "Status", "Type", "Start", "End"],
35+
title=utils.clean_splitlines(event.get('subject')))
36+
37+
table.add_row([
38+
event.get('id'),
39+
utils.lookup(event, 'statusCode', 'name'),
40+
utils.lookup(event, 'notificationOccurrenceEventType', 'keyName'),
41+
utils.clean_time(event.get('startDate')),
42+
utils.clean_time(event.get('endDate'))
43+
])
44+
45+
return table
46+
47+
48+
def impacted_table(event):
49+
"""Formats a basic impacted resources table"""
50+
table = formatting.Table([
51+
"Type", "Id", "Hostname", "PrivateIp", "Label"
52+
])
53+
for item in event.get('impactedResources', []):
54+
table.add_row([
55+
item.get('resourceType'),
56+
item.get('resourceTableId'),
57+
item.get('hostname'),
58+
item.get('privateIp'),
59+
item.get('filterLabel')
60+
])
61+
return table
62+
63+
64+
def update_table(event):
65+
"""Formats a basic event update table"""
66+
update_number = 0
67+
for update in event.get('updates', []):
68+
header = "======= Update #%s on %s =======" % (update_number, utils.clean_time(update.get('startDate')))
69+
click.secho(header, fg='green')
70+
update_number = update_number + 1
71+
text = update.get('contents')
72+
# deals with all the \r\n from the API
73+
click.secho(utils.clean_splitlines(text))

SoftLayer/CLI/account/events.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
"""Summary and acknowledgement of upcoming and ongoing maintenance events"""
2+
# :license: MIT, see LICENSE for more details.
3+
import click
4+
5+
from SoftLayer.CLI import environment
6+
from SoftLayer.CLI import formatting
7+
from SoftLayer.managers.account import AccountManager as AccountManager
8+
from SoftLayer import utils
9+
10+
11+
@click.command()
12+
@click.option('--ack-all', is_flag=True, default=False,
13+
help="Acknowledge every upcoming event. Doing so will turn off the popup in the control portal")
14+
@environment.pass_env
15+
def cli(env, ack_all):
16+
"""Summary and acknowledgement of upcoming and ongoing maintenance events"""
17+
18+
manager = AccountManager(env.client)
19+
events = manager.get_upcoming_events()
20+
21+
if ack_all:
22+
for event in events:
23+
result = manager.ack_event(event['id'])
24+
event['acknowledgedFlag'] = result
25+
env.fout(event_table(events))
26+
27+
28+
def event_table(events):
29+
"""Formats a table for events"""
30+
table = formatting.Table([
31+
"Id",
32+
"Start Date",
33+
"End Date",
34+
"Subject",
35+
"Status",
36+
"Acknowledged",
37+
"Updates",
38+
"Impacted Resources"
39+
], title="Upcoming Events")
40+
table.align['Subject'] = 'l'
41+
table.align['Impacted Resources'] = 'l'
42+
for event in events:
43+
table.add_row([
44+
event.get('id'),
45+
utils.clean_time(event.get('startDate')),
46+
utils.clean_time(event.get('endDate')),
47+
# Some subjects can have \r\n for some reason.
48+
utils.clean_splitlines(event.get('subject')),
49+
utils.lookup(event, 'statusCode', 'name'),
50+
event.get('acknowledgedFlag'),
51+
event.get('updateCount'),
52+
event.get('impactedResourceCount')
53+
])
54+
return table
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""Invoice details"""
2+
# :license: MIT, see LICENSE for more details.
3+
4+
import click
5+
6+
from SoftLayer.CLI import environment
7+
from SoftLayer.CLI import formatting
8+
from SoftLayer.managers.account import AccountManager as AccountManager
9+
from SoftLayer import utils
10+
11+
12+
@click.command()
13+
@click.argument('identifier')
14+
@click.option('--details', is_flag=True, default=False, show_default=True,
15+
help="Shows a very detailed list of charges")
16+
@environment.pass_env
17+
def cli(env, identifier, details):
18+
"""Invoices and all that mess"""
19+
20+
manager = AccountManager(env.client)
21+
top_items = manager.get_billing_items(identifier)
22+
23+
title = "Invoice %s" % identifier
24+
table = formatting.Table(["Item Id", "Category", "Description", "Single",
25+
"Monthly", "Create Date", "Location"], title=title)
26+
table.align['category'] = 'l'
27+
table.align['description'] = 'l'
28+
for item in top_items:
29+
fqdn = "%s.%s" % (item.get('hostName', ''), item.get('domainName', ''))
30+
# category id=2046, ram_usage doesn't have a name...
31+
category = utils.lookup(item, 'category', 'name') or item.get('categoryCode')
32+
description = nice_string(item.get('description'))
33+
if fqdn != '.':
34+
description = "%s (%s)" % (item.get('description'), fqdn)
35+
table.add_row([
36+
item.get('id'),
37+
category,
38+
nice_string(description),
39+
"$%.2f" % float(item.get('oneTimeAfterTaxAmount')),
40+
"$%.2f" % float(item.get('recurringAfterTaxAmount')),
41+
utils.clean_time(item.get('createDate'), out_format="%Y-%m-%d"),
42+
utils.lookup(item, 'location', 'name')
43+
])
44+
if details:
45+
for child in item.get('children', []):
46+
table.add_row([
47+
'>>>',
48+
utils.lookup(child, 'category', 'name'),
49+
nice_string(child.get('description')),
50+
"$%.2f" % float(child.get('oneTimeAfterTaxAmount')),
51+
"$%.2f" % float(child.get('recurringAfterTaxAmount')),
52+
'---',
53+
'---'
54+
])
55+
56+
env.fout(table)
57+
58+
59+
def nice_string(ugly_string, limit=100):
60+
"""Format and trims strings"""
61+
return (ugly_string[:limit] + '..') if len(ugly_string) > limit else ugly_string

SoftLayer/CLI/account/invoices.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"""Invoice listing"""
2+
# :license: MIT, see LICENSE for more details.
3+
4+
import click
5+
6+
from SoftLayer.CLI import environment
7+
from SoftLayer.CLI import formatting
8+
from SoftLayer.managers.account import AccountManager as AccountManager
9+
from SoftLayer import utils
10+
11+
12+
@click.command()
13+
@click.option('--limit', default=50, show_default=True,
14+
help="How many invoices to get back.")
15+
@click.option('--closed', is_flag=True, default=False, show_default=True,
16+
help="Include invoices with a CLOSED status.")
17+
@click.option('--all', 'get_all', is_flag=True, default=False, show_default=True,
18+
help="Return ALL invoices. There may be a lot of these.")
19+
@environment.pass_env
20+
def cli(env, limit, closed=False, get_all=False):
21+
"""Invoices and all that mess"""
22+
23+
manager = AccountManager(env.client)
24+
invoices = manager.get_invoices(limit, closed, get_all)
25+
26+
table = formatting.Table([
27+
"Id", "Created", "Type", "Status", "Starting Balance", "Ending Balance", "Invoice Amount", "Items"
28+
])
29+
table.align['Starting Balance'] = 'l'
30+
table.align['Ending Balance'] = 'l'
31+
table.align['Invoice Amount'] = 'l'
32+
table.align['Items'] = 'l'
33+
if isinstance(invoices, dict):
34+
invoices = [invoices]
35+
for invoice in invoices:
36+
table.add_row([
37+
invoice.get('id'),
38+
utils.clean_time(invoice.get('createDate'), out_format="%Y-%m-%d"),
39+
invoice.get('typeCode'),
40+
invoice.get('statusCode'),
41+
invoice.get('startingBalance'),
42+
invoice.get('endingBalance'),
43+
invoice.get('invoiceTotalAmount'),
44+
invoice.get('itemCount')
45+
])
46+
env.fout(table)

SoftLayer/CLI/account/summary.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"""Account Summary page"""
2+
# :license: MIT, see LICENSE for more details.
3+
import click
4+
5+
from SoftLayer.CLI import environment
6+
from SoftLayer.CLI import formatting
7+
from SoftLayer.managers.account import AccountManager as AccountManager
8+
from SoftLayer import utils
9+
10+
11+
@click.command()
12+
@environment.pass_env
13+
def cli(env):
14+
"""Prints some various bits of information about an account"""
15+
16+
manager = AccountManager(env.client)
17+
summary = manager.get_summary()
18+
env.fout(get_snapshot_table(summary))
19+
20+
21+
def get_snapshot_table(account):
22+
"""Generates a table for printing account summary data"""
23+
table = formatting.KeyValueTable(["Name", "Value"], title="Account Snapshot")
24+
table.align['Name'] = 'r'
25+
table.align['Value'] = 'l'
26+
table.add_row(['Company Name', account.get('companyName', '-')])
27+
table.add_row(['Balance', utils.lookup(account, 'pendingInvoice', 'startingBalance')])
28+
table.add_row(['Upcoming Invoice', utils.lookup(account, 'pendingInvoice', 'invoiceTotalAmount')])
29+
table.add_row(['Image Templates', account.get('blockDeviceTemplateGroupCount', '-')])
30+
table.add_row(['Dedicated Hosts', account.get('dedicatedHostCount', '-')])
31+
table.add_row(['Hardware', account.get('hardwareCount', '-')])
32+
table.add_row(['Virtual Guests', account.get('virtualGuestCount', '-')])
33+
table.add_row(['Domains', account.get('domainCount', '-')])
34+
table.add_row(['Network Storage Volumes', account.get('networkStorageCount', '-')])
35+
table.add_row(['Open Tickets', account.get('openTicketCount', '-')])
36+
table.add_row(['Network Vlans', account.get('networkVlanCount', '-')])
37+
table.add_row(['Subnets', account.get('subnetCount', '-')])
38+
table.add_row(['Users', account.get('userCount', '-')])
39+
return table

SoftLayer/CLI/routes.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@
1111

1212
('call-api', 'SoftLayer.CLI.call_api:cli'),
1313

14+
('account', 'SoftLayer.CLI.account'),
15+
('account:invoice-detail', 'SoftLayer.CLI.account.invoice_detail:cli'),
16+
('account:invoices', 'SoftLayer.CLI.account.invoices:cli'),
17+
('account:events', 'SoftLayer.CLI.account.events:cli'),
18+
('account:event-detail', 'SoftLayer.CLI.account.event_detail:cli'),
19+
('account:summary', 'SoftLayer.CLI.account.summary:cli'),
20+
1421
('virtual', 'SoftLayer.CLI.virt'),
1522
('virtual:cancel', 'SoftLayer.CLI.virt.cancel:cli'),
1623
('virtual:capture', 'SoftLayer.CLI.virt.capture:cli'),

SoftLayer/fixtures/SoftLayer_Account.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,3 +662,27 @@
662662
"name": "SPREAD"
663663
}
664664
}]
665+
666+
getInvoices = [
667+
{
668+
'id': 33816665,
669+
'modifyDate': '2019-03-04T00:17:42-06:00',
670+
'createDate': '2019-03-04T00:17:42-06:00',
671+
'startingBalance': '129251.73',
672+
'statusCode': 'OPEN',
673+
'typeCode': 'RECURRING',
674+
'itemCount': 3317,
675+
'invoiceTotalAmount': '6230.66'
676+
},
677+
{
678+
'id': 12345667,
679+
'modifyDate': '2019-03-05T00:17:42-06:00',
680+
'createDate': '2019-03-04T00:17:42-06:00',
681+
'startingBalance': '129251.73',
682+
'statusCode': 'OPEN',
683+
'typeCode': 'RECURRING',
684+
'itemCount': 12,
685+
'invoiceTotalAmount': '6230.66',
686+
'endingBalance': '12345.55'
687+
}
688+
]
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
getInvoiceTopLevelItems = [
2+
{
3+
'categoryCode': 'sov_sec_ip_addresses_priv',
4+
'createDate': '2018-04-04T23:15:20-06:00',
5+
'description': '64 Portable Private IP Addresses',
6+
'id': 724951323,
7+
'oneTimeAfterTaxAmount': '0',
8+
'recurringAfterTaxAmount': '0',
9+
'hostName': 'bleg',
10+
'domainName': 'beh.com',
11+
'category': {'name': 'Private (only) Secondary VLAN IP Addresses'},
12+
'children': [
13+
{
14+
'id': 12345,
15+
'category': {'name': 'Fake Child Category'},
16+
'description': 'Blah',
17+
'oneTimeAfterTaxAmount': 55.50,
18+
'recurringAfterTaxAmount': 0.10
19+
}
20+
],
21+
'location': {'name': 'fra02'}
22+
}
23+
]
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
getObject = {
2+
'endDate': '2019-03-18T17:00:00-06:00',
3+
'id': 1234,
4+
'lastImpactedUserCount': 417756,
5+
'modifyDate': '2019-03-12T15:32:48-06:00',
6+
'recoveryTime': None,
7+
'startDate': '2019-03-18T16:00:00-06:00',
8+
'subject': 'Public Website Maintenance',
9+
'summary': 'Blah Blah Blah',
10+
'systemTicketId': 76057381,
11+
'acknowledgedFlag': False,
12+
'attachments': [],
13+
'impactedResources': [{
14+
'resourceType': 'Server',
15+
'resourceTableId': 12345,
16+
'hostname': 'test',
17+
'privateIp': '10.0.0.1',
18+
'filterLable': 'Server'
19+
}],
20+
'notificationOccurrenceEventType': {'keyName': 'PLANNED'},
21+
'statusCode': {'keyName': 'PUBLISHED', 'name': 'Published'},
22+
'updates': [{
23+
'contents': 'More Blah Blah',
24+
'createDate': '2019-03-12T13:07:22-06:00',
25+
'endDate': None, 'startDate': '2019-03-12T13:07:22-06:00'
26+
}]
27+
}
28+
29+
getAllObjects = [getObject]
30+
31+
acknowledgeNotification = True

0 commit comments

Comments
 (0)