diff --git a/SoftLayer/CLI/account/__init__.py b/SoftLayer/CLI/account/__init__.py new file mode 100644 index 000000000..50da7c7f0 --- /dev/null +++ b/SoftLayer/CLI/account/__init__.py @@ -0,0 +1 @@ +"""Account commands""" diff --git a/SoftLayer/CLI/account/event_detail.py b/SoftLayer/CLI/account/event_detail.py new file mode 100644 index 000000000..2c1ee80c2 --- /dev/null +++ b/SoftLayer/CLI/account/event_detail.py @@ -0,0 +1,73 @@ +"""Details of a specific event, and ability to acknowledge event.""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers.account import AccountManager as AccountManager +from SoftLayer import utils + + +@click.command() +@click.argument('identifier') +@click.option('--ack', is_flag=True, default=False, + help="Acknowledge Event. Doing so will turn off the popup in the control portal") +@environment.pass_env +def cli(env, identifier, ack): + """Details of a specific event, and ability to acknowledge event.""" + + # Print a list of all on going maintenance + manager = AccountManager(env.client) + event = manager.get_event(identifier) + + if ack: + manager.ack_event(identifier) + + env.fout(basic_event_table(event)) + env.fout(impacted_table(event)) + env.fout(update_table(event)) + + +def basic_event_table(event): + """Formats a basic event table""" + table = formatting.Table(["Id", "Status", "Type", "Start", "End"], + title=utils.clean_splitlines(event.get('subject'))) + + table.add_row([ + event.get('id'), + utils.lookup(event, 'statusCode', 'name'), + utils.lookup(event, 'notificationOccurrenceEventType', 'keyName'), + utils.clean_time(event.get('startDate')), + utils.clean_time(event.get('endDate')) + ]) + + return table + + +def impacted_table(event): + """Formats a basic impacted resources table""" + table = formatting.Table([ + "Type", "Id", "Hostname", "PrivateIp", "Label" + ]) + for item in event.get('impactedResources', []): + table.add_row([ + item.get('resourceType'), + item.get('resourceTableId'), + item.get('hostname'), + item.get('privateIp'), + item.get('filterLabel') + ]) + return table + + +def update_table(event): + """Formats a basic event update table""" + update_number = 0 + for update in event.get('updates', []): + header = "======= Update #%s on %s =======" % (update_number, utils.clean_time(update.get('startDate'))) + click.secho(header, fg='green') + update_number = update_number + 1 + text = update.get('contents') + # deals with all the \r\n from the API + click.secho(utils.clean_splitlines(text)) diff --git a/SoftLayer/CLI/account/events.py b/SoftLayer/CLI/account/events.py new file mode 100644 index 000000000..5cc91144d --- /dev/null +++ b/SoftLayer/CLI/account/events.py @@ -0,0 +1,54 @@ +"""Summary and acknowledgement of upcoming and ongoing maintenance events""" +# :license: MIT, see LICENSE for more details. +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers.account import AccountManager as AccountManager +from SoftLayer import utils + + +@click.command() +@click.option('--ack-all', is_flag=True, default=False, + help="Acknowledge every upcoming event. Doing so will turn off the popup in the control portal") +@environment.pass_env +def cli(env, ack_all): + """Summary and acknowledgement of upcoming and ongoing maintenance events""" + + manager = AccountManager(env.client) + events = manager.get_upcoming_events() + + if ack_all: + for event in events: + result = manager.ack_event(event['id']) + event['acknowledgedFlag'] = result + env.fout(event_table(events)) + + +def event_table(events): + """Formats a table for events""" + table = formatting.Table([ + "Id", + "Start Date", + "End Date", + "Subject", + "Status", + "Acknowledged", + "Updates", + "Impacted Resources" + ], title="Upcoming Events") + table.align['Subject'] = 'l' + table.align['Impacted Resources'] = 'l' + for event in events: + table.add_row([ + event.get('id'), + utils.clean_time(event.get('startDate')), + utils.clean_time(event.get('endDate')), + # Some subjects can have \r\n for some reason. + utils.clean_splitlines(event.get('subject')), + utils.lookup(event, 'statusCode', 'name'), + event.get('acknowledgedFlag'), + event.get('updateCount'), + event.get('impactedResourceCount') + ]) + return table diff --git a/SoftLayer/CLI/account/invoice_detail.py b/SoftLayer/CLI/account/invoice_detail.py new file mode 100644 index 000000000..45343184e --- /dev/null +++ b/SoftLayer/CLI/account/invoice_detail.py @@ -0,0 +1,61 @@ +"""Invoice details""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers.account import AccountManager as AccountManager +from SoftLayer import utils + + +@click.command() +@click.argument('identifier') +@click.option('--details', is_flag=True, default=False, show_default=True, + help="Shows a very detailed list of charges") +@environment.pass_env +def cli(env, identifier, details): + """Invoices and all that mess""" + + manager = AccountManager(env.client) + top_items = manager.get_billing_items(identifier) + + title = "Invoice %s" % identifier + table = formatting.Table(["Item Id", "Category", "Description", "Single", + "Monthly", "Create Date", "Location"], title=title) + table.align['category'] = 'l' + table.align['description'] = 'l' + for item in top_items: + fqdn = "%s.%s" % (item.get('hostName', ''), item.get('domainName', '')) + # category id=2046, ram_usage doesn't have a name... + category = utils.lookup(item, 'category', 'name') or item.get('categoryCode') + description = nice_string(item.get('description')) + if fqdn != '.': + description = "%s (%s)" % (item.get('description'), fqdn) + table.add_row([ + item.get('id'), + category, + nice_string(description), + "$%.2f" % float(item.get('oneTimeAfterTaxAmount')), + "$%.2f" % float(item.get('recurringAfterTaxAmount')), + utils.clean_time(item.get('createDate'), out_format="%Y-%m-%d"), + utils.lookup(item, 'location', 'name') + ]) + if details: + for child in item.get('children', []): + table.add_row([ + '>>>', + utils.lookup(child, 'category', 'name'), + nice_string(child.get('description')), + "$%.2f" % float(child.get('oneTimeAfterTaxAmount')), + "$%.2f" % float(child.get('recurringAfterTaxAmount')), + '---', + '---' + ]) + + env.fout(table) + + +def nice_string(ugly_string, limit=100): + """Format and trims strings""" + return (ugly_string[:limit] + '..') if len(ugly_string) > limit else ugly_string diff --git a/SoftLayer/CLI/account/invoices.py b/SoftLayer/CLI/account/invoices.py new file mode 100644 index 000000000..1610ed11e --- /dev/null +++ b/SoftLayer/CLI/account/invoices.py @@ -0,0 +1,46 @@ +"""Invoice listing""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers.account import AccountManager as AccountManager +from SoftLayer import utils + + +@click.command() +@click.option('--limit', default=50, show_default=True, + help="How many invoices to get back.") +@click.option('--closed', is_flag=True, default=False, show_default=True, + help="Include invoices with a CLOSED status.") +@click.option('--all', 'get_all', is_flag=True, default=False, show_default=True, + help="Return ALL invoices. There may be a lot of these.") +@environment.pass_env +def cli(env, limit, closed=False, get_all=False): + """Invoices and all that mess""" + + manager = AccountManager(env.client) + invoices = manager.get_invoices(limit, closed, get_all) + + table = formatting.Table([ + "Id", "Created", "Type", "Status", "Starting Balance", "Ending Balance", "Invoice Amount", "Items" + ]) + table.align['Starting Balance'] = 'l' + table.align['Ending Balance'] = 'l' + table.align['Invoice Amount'] = 'l' + table.align['Items'] = 'l' + if isinstance(invoices, dict): + invoices = [invoices] + for invoice in invoices: + table.add_row([ + invoice.get('id'), + utils.clean_time(invoice.get('createDate'), out_format="%Y-%m-%d"), + invoice.get('typeCode'), + invoice.get('statusCode'), + invoice.get('startingBalance'), + invoice.get('endingBalance'), + invoice.get('invoiceTotalAmount'), + invoice.get('itemCount') + ]) + env.fout(table) diff --git a/SoftLayer/CLI/account/summary.py b/SoftLayer/CLI/account/summary.py new file mode 100644 index 000000000..f1ae2b6be --- /dev/null +++ b/SoftLayer/CLI/account/summary.py @@ -0,0 +1,39 @@ +"""Account Summary page""" +# :license: MIT, see LICENSE for more details. +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers.account import AccountManager as AccountManager +from SoftLayer import utils + + +@click.command() +@environment.pass_env +def cli(env): + """Prints some various bits of information about an account""" + + manager = AccountManager(env.client) + summary = manager.get_summary() + env.fout(get_snapshot_table(summary)) + + +def get_snapshot_table(account): + """Generates a table for printing account summary data""" + table = formatting.KeyValueTable(["Name", "Value"], title="Account Snapshot") + table.align['Name'] = 'r' + table.align['Value'] = 'l' + table.add_row(['Company Name', account.get('companyName', '-')]) + table.add_row(['Balance', utils.lookup(account, 'pendingInvoice', 'startingBalance')]) + table.add_row(['Upcoming Invoice', utils.lookup(account, 'pendingInvoice', 'invoiceTotalAmount')]) + table.add_row(['Image Templates', account.get('blockDeviceTemplateGroupCount', '-')]) + table.add_row(['Dedicated Hosts', account.get('dedicatedHostCount', '-')]) + table.add_row(['Hardware', account.get('hardwareCount', '-')]) + table.add_row(['Virtual Guests', account.get('virtualGuestCount', '-')]) + table.add_row(['Domains', account.get('domainCount', '-')]) + table.add_row(['Network Storage Volumes', account.get('networkStorageCount', '-')]) + table.add_row(['Open Tickets', account.get('openTicketCount', '-')]) + table.add_row(['Network Vlans', account.get('networkVlanCount', '-')]) + table.add_row(['Subnets', account.get('subnetCount', '-')]) + table.add_row(['Users', account.get('userCount', '-')]) + return table diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index b31144083..c04233c52 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -11,6 +11,13 @@ ('call-api', 'SoftLayer.CLI.call_api:cli'), + ('account', 'SoftLayer.CLI.account'), + ('account:invoice-detail', 'SoftLayer.CLI.account.invoice_detail:cli'), + ('account:invoices', 'SoftLayer.CLI.account.invoices:cli'), + ('account:events', 'SoftLayer.CLI.account.events:cli'), + ('account:event-detail', 'SoftLayer.CLI.account.event_detail:cli'), + ('account:summary', 'SoftLayer.CLI.account.summary:cli'), + ('virtual', 'SoftLayer.CLI.virt'), ('virtual:cancel', 'SoftLayer.CLI.virt.cancel:cli'), ('virtual:capture', 'SoftLayer.CLI.virt.capture:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index b4bafac92..bd5a45d0e 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -662,3 +662,27 @@ "name": "SPREAD" } }] + +getInvoices = [ + { + 'id': 33816665, + 'modifyDate': '2019-03-04T00:17:42-06:00', + 'createDate': '2019-03-04T00:17:42-06:00', + 'startingBalance': '129251.73', + 'statusCode': 'OPEN', + 'typeCode': 'RECURRING', + 'itemCount': 3317, + 'invoiceTotalAmount': '6230.66' + }, + { + 'id': 12345667, + 'modifyDate': '2019-03-05T00:17:42-06:00', + 'createDate': '2019-03-04T00:17:42-06:00', + 'startingBalance': '129251.73', + 'statusCode': 'OPEN', + 'typeCode': 'RECURRING', + 'itemCount': 12, + 'invoiceTotalAmount': '6230.66', + 'endingBalance': '12345.55' + } +] diff --git a/SoftLayer/fixtures/SoftLayer_Billing_Invoice.py b/SoftLayer/fixtures/SoftLayer_Billing_Invoice.py new file mode 100644 index 000000000..d4d89131c --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Billing_Invoice.py @@ -0,0 +1,23 @@ +getInvoiceTopLevelItems = [ + { + 'categoryCode': 'sov_sec_ip_addresses_priv', + 'createDate': '2018-04-04T23:15:20-06:00', + 'description': '64 Portable Private IP Addresses', + 'id': 724951323, + 'oneTimeAfterTaxAmount': '0', + 'recurringAfterTaxAmount': '0', + 'hostName': 'bleg', + 'domainName': 'beh.com', + 'category': {'name': 'Private (only) Secondary VLAN IP Addresses'}, + 'children': [ + { + 'id': 12345, + 'category': {'name': 'Fake Child Category'}, + 'description': 'Blah', + 'oneTimeAfterTaxAmount': 55.50, + 'recurringAfterTaxAmount': 0.10 + } + ], + 'location': {'name': 'fra02'} + } +] diff --git a/SoftLayer/fixtures/SoftLayer_Notification_Occurrence_Event.py b/SoftLayer/fixtures/SoftLayer_Notification_Occurrence_Event.py new file mode 100644 index 000000000..7c6740431 --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Notification_Occurrence_Event.py @@ -0,0 +1,31 @@ +getObject = { + 'endDate': '2019-03-18T17:00:00-06:00', + 'id': 1234, + 'lastImpactedUserCount': 417756, + 'modifyDate': '2019-03-12T15:32:48-06:00', + 'recoveryTime': None, + 'startDate': '2019-03-18T16:00:00-06:00', + 'subject': 'Public Website Maintenance', + 'summary': 'Blah Blah Blah', + 'systemTicketId': 76057381, + 'acknowledgedFlag': False, + 'attachments': [], + 'impactedResources': [{ + 'resourceType': 'Server', + 'resourceTableId': 12345, + 'hostname': 'test', + 'privateIp': '10.0.0.1', + 'filterLable': 'Server' + }], + 'notificationOccurrenceEventType': {'keyName': 'PLANNED'}, + 'statusCode': {'keyName': 'PUBLISHED', 'name': 'Published'}, + 'updates': [{ + 'contents': 'More Blah Blah', + 'createDate': '2019-03-12T13:07:22-06:00', + 'endDate': None, 'startDate': '2019-03-12T13:07:22-06:00' + }] +} + +getAllObjects = [getObject] + +acknowledgeNotification = True diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py new file mode 100644 index 000000000..1f7d4871d --- /dev/null +++ b/SoftLayer/managers/account.py @@ -0,0 +1,141 @@ +""" + SoftLayer.account + ~~~~~~~~~~~~~~~~~~~~~~~ + Account manager + + :license: MIT, see License for more details. +""" + +import logging + +from SoftLayer import utils + +# Invalid names are ignored due to long method names and short argument names +# pylint: disable=invalid-name, no-self-use + +LOGGER = logging.getLogger(__name__) + + +class AccountManager(utils.IdentifierMixin, object): + """Common functions for getting information from the Account service + + :param SoftLayer.API.BaseClient client: the client instance + """ + + def __init__(self, client): + self.client = client + + def get_summary(self): + """Gets some basic account information + + :return: Account object + """ + mask = """mask[ + nextInvoiceTotalAmount, + pendingInvoice[invoiceTotalAmount], + blockDeviceTemplateGroupCount, + dedicatedHostCount, + domainCount, + hardwareCount, + networkStorageCount, + openTicketCount, + networkVlanCount, + subnetCount, + userCount, + virtualGuestCount + ] + """ + return self.client.call('Account', 'getObject', mask=mask) + + def get_upcoming_events(self): + """Retreives a list of Notification_Occurrence_Events that have not ended yet + + :return: SoftLayer_Notification_Occurrence_Event + """ + mask = "mask[id, subject, startDate, endDate, statusCode, acknowledgedFlag, impactedResourceCount, updateCount]" + _filter = { + 'endDate': { + 'operation': '> sysdate' + }, + 'startDate': { + 'operation': 'orderBy', + 'options': [{ + 'name': 'sort', + 'value': ['ASC'] + }] + } + } + return self.client.call('Notification_Occurrence_Event', 'getAllObjects', filter=_filter, mask=mask, iter=True) + + def ack_event(self, event_id): + """Acknowledge an event. This mostly prevents it from appearing as a notification in the control portal. + + :param int event_id: Notification_Occurrence_Event ID you want to ack + :return: True on success, Exception otherwise. + """ + return self.client.call('Notification_Occurrence_Event', 'acknowledgeNotification', id=event_id) + + def get_event(self, event_id): + """Gets details about a maintenance event + + :param int event_id: Notification_Occurrence_Event ID + :return: Notification_Occurrence_Event + """ + mask = """mask[ + acknowledgedFlag, + attachments, + impactedResources, + statusCode, + updates, + notificationOccurrenceEventType] + """ + return self.client.call('Notification_Occurrence_Event', 'getObject', id=event_id, mask=mask) + + def get_invoices(self, limit=50, closed=False, get_all=False): + """Gets an accounts invoices. + + :param int limit: Number of invoices to get back in a single call. + :param bool closed: If True, will also get CLOSED invoices + :param bool get_all: If True, will paginate through invoices until all have been retrieved. + :return: Billing_Invoice + """ + mask = "mask[invoiceTotalAmount, itemCount]" + _filter = { + 'invoices': { + 'createDate': { + 'operation': 'orderBy', + 'options': [{ + 'name': 'sort', + 'value': ['DESC'] + }] + }, + 'statusCode': {'operation': 'OPEN'}, + } + } + if closed: + del _filter['invoices']['statusCode'] + + return self.client.call('Account', 'getInvoices', mask=mask, filter=_filter, iter=get_all, limit=limit) + + def get_billing_items(self, identifier): + """Gets all topLevelBillingItems from a specific invoice + + :param int identifier: Invoice Id + :return: Billing_Invoice_Item + """ + + mask = """mask[ + id, description, hostName, domainName, oneTimeAfterTaxAmount, recurringAfterTaxAmount, createDate, + categoryCode, + category[name], + location[name], + children[id, category[name], description, oneTimeAfterTaxAmount, recurringAfterTaxAmount] + ]""" + return self.client.call( + 'Billing_Invoice', + 'getInvoiceTopLevelItems', + id=identifier, + mask=mask, + iter=True, + limit=100 + ) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 5c68f7c49..f4904adf6 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -142,9 +142,9 @@ def format_event_log_date(date_string, utc): utc = "+0000" iso_time_zone = utc[:3] + ':' + utc[3:] - clean_time = "{}.000000{}".format(dirty_time, iso_time_zone) + cleaned_time = "{}.000000{}".format(dirty_time, iso_time_zone) - return clean_time + return cleaned_time def event_log_filter_between_date(start, end, utc): @@ -288,3 +288,26 @@ def clean_string(string): return '' else: return " ".join(string.split()) + + +def clean_splitlines(string): + """Returns a string where \r\n is replaced with \n""" + if string is None: + return '' + else: + return "\n".join(string.splitlines()) + + +def clean_time(sltime, in_format='%Y-%m-%dT%H:%M:%S%z', out_format='%Y-%m-%d %H:%M'): + """Easy way to format time strings + + :param string sltime: A softlayer formatted time string + :param string in_format: Datetime format for strptime + :param string out_format: Datetime format for strftime + """ + try: + clean = datetime.datetime.strptime(sltime, in_format) + return clean.strftime(out_format) + # The %z option only exists with py3.6+ + except ValueError: + return sltime diff --git a/docs/api/managers/account.rst b/docs/api/managers/account.rst new file mode 100644 index 000000000..25d76ed6a --- /dev/null +++ b/docs/api/managers/account.rst @@ -0,0 +1,5 @@ +.. _account: + +.. automodule:: SoftLayer.managers.account + :members: + :inherited-members: \ No newline at end of file diff --git a/docs/cli.rst b/docs/cli.rst index 6d8b18218..709741aa1 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -12,12 +12,12 @@ functionality not fully documented here. .. toctree:: :maxdepth: 2 - cli/ipsec + cli/account cli/vs cli/hardware cli/ordering cli/users - + cli/ipsec .. _config_setup: diff --git a/docs/cli/account.rst b/docs/cli/account.rst new file mode 100644 index 000000000..9b3ad6954 --- /dev/null +++ b/docs/cli/account.rst @@ -0,0 +1,25 @@ +.. _cli_account: + +Account Commands +================= + + +.. click:: SoftLayer.CLI.account.summary:cli + :prog: account summary + :show-nested: + +.. click:: SoftLayer.CLI.account.events:cli + :prog: account events + :show-nested: + +.. click:: SoftLayer.CLI.account.event_detail:cli + :prog: account event-detail + :show-nested: + +.. click:: SoftLayer.CLI.account.invoices:cli + :prog: account invoices + :show-nested: + +.. click:: SoftLayer.CLI.account.invoice_detail:cli + :prog: account invoice-detail + :show-nested: \ No newline at end of file diff --git a/tests/CLI/modules/account_tests.py b/tests/CLI/modules/account_tests.py new file mode 100644 index 000000000..c495546c8 --- /dev/null +++ b/tests/CLI/modules/account_tests.py @@ -0,0 +1,95 @@ +""" + SoftLayer.tests.CLI.modules.account_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Tests for the user cli command +""" +from SoftLayer.fixtures import SoftLayer_Account as SoftLayer_Account +from SoftLayer import testing + + +class AccountCLITests(testing.TestCase): + + def set_up(self): + self.SLNOE = 'SoftLayer_Notification_Occurrence_Event' + + # slcli account event-detail + def test_event_detail(self): + result = self.run_command(['account', 'event-detail', '1234']) + self.assert_no_fail(result) + self.assert_called_with(self.SLNOE, 'getObject', identifier='1234') + + def test_event_details_ack(self): + result = self.run_command(['account', 'event-detail', '1234', '--ack']) + self.assert_no_fail(result) + self.assert_called_with(self.SLNOE, 'getObject', identifier='1234') + self.assert_called_with(self.SLNOE, 'acknowledgeNotification', identifier='1234') + + # slcli account events + def test_events(self): + result = self.run_command(['account', 'events']) + self.assert_no_fail(result) + self.assert_called_with(self.SLNOE, 'getAllObjects') + + def test_event_ack_all(self): + result = self.run_command(['account', 'events', '--ack-all']) + self.assert_no_fail(result) + self.assert_called_with(self.SLNOE, 'getAllObjects') + self.assert_called_with(self.SLNOE, 'acknowledgeNotification', identifier=1234) + + # slcli account invoice-detail + + def test_invoice_detail(self): + result = self.run_command(['account', 'invoice-detail', '1234']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Billing_Invoice', 'getInvoiceTopLevelItems', identifier='1234') + + def test_invoice_detail_details(self): + result = self.run_command(['account', 'invoice-detail', '1234', '--details']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Billing_Invoice', 'getInvoiceTopLevelItems', identifier='1234') + + # slcli account invoices + def test_invoices(self): + result = self.run_command(['account', 'invoices']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', 'getInvoices', limit=50) + + def test_invoices_limited(self): + result = self.run_command(['account', 'invoices', '--limit=10']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', 'getInvoices', limit=10) + + def test_invoices_closed(self): + _filter = { + 'invoices': { + 'createDate': { + 'operation': 'orderBy', + 'options': [{ + 'name': 'sort', + 'value': ['DESC'] + }] + } + } + } + result = self.run_command(['account', 'invoices', '--closed']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', 'getInvoices', limit=50, filter=_filter) + + def test_invoices_all(self): + result = self.run_command(['account', 'invoices', '--all']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', 'getInvoices', limit=50) + + def test_single_invoice(self): + amock = self.set_mock('SoftLayer_Account', 'getInvoices') + amock.return_value = SoftLayer_Account.getInvoices[0] + result = self.run_command(['account', 'invoices', '--limit=1']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', 'getInvoices', limit=1) + + # slcli account summary + def test_account_summary(self): + result = self.run_command(['account', 'summary']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', 'getObject') diff --git a/tests/CLI/modules/event_log_tests.py b/tests/CLI/modules/event_log_tests.py index a7ff0dcb1..b36ff8530 100644 --- a/tests/CLI/modules/event_log_tests.py +++ b/tests/CLI/modules/event_log_tests.py @@ -36,7 +36,7 @@ def test_get_event_log_with_metadata(self): '"portRangeMax":2001,"portRangeMin":2000,"protocol":"tcp",' '"remoteGroupId":null,"remoteIp":null,"ruleId":"100"}],"securityGroupId":"200",' '"securityGroupName":"test_SG"}' - ), + ), indent=4, sort_keys=True ) @@ -53,7 +53,7 @@ def test_get_event_log_with_metadata(self): '"requestId":"96c9b47b9e102d2e1d81fba",' '"securityGroupId":"200",' '"securityGroupName":"test_SG"}' - ), + ), indent=4, sort_keys=True ) @@ -70,7 +70,7 @@ def test_get_event_log_with_metadata(self): '"ethertype":"IPv4",' '"portRangeMax":2001,"portRangeMin":2000,"protocol":"tcp",' '"remoteGroupId":null,"remoteIp":null,"ruleId":"800"}]}' - ), + ), indent=4, sort_keys=True ) @@ -86,7 +86,7 @@ def test_get_event_log_with_metadata(self): '"networkComponentId":"100",' '"networkInterfaceType":"public",' '"requestId":"6b9a87a9ab8ac9a22e87a00"}' - ), + ), indent=4, sort_keys=True ) @@ -103,7 +103,7 @@ def test_get_event_log_with_metadata(self): '"ethertype":"IPv4",' '"portRangeMax":2001,"portRangeMin":2000,"protocol":"tcp",' '"remoteGroupId":null,"remoteIp":null,"ruleId":"800"}]}' - ), + ), indent=4, sort_keys=True ) @@ -119,7 +119,7 @@ def test_get_event_log_with_metadata(self): '"networkComponentId":"100",' '"networkInterfaceType":"public",' '"requestId":"4709e02ad42c83f80345904"}' - ), + ), indent=4, sort_keys=True ) @@ -216,7 +216,7 @@ def test_get_event_table(self): '"portRangeMax":2001,"portRangeMin":2000,"protocol":"tcp",' '"remoteGroupId":null,"remoteIp":null,"ruleId":"100"}],"securityGroupId":"200",' '"securityGroupName":"test_SG"}' - ), + ), indent=4, sort_keys=True ) @@ -233,7 +233,7 @@ def test_get_event_table(self): '"requestId":"96c9b47b9e102d2e1d81fba",' '"securityGroupId":"200",' '"securityGroupName":"test_SG"}' - ), + ), indent=4, sort_keys=True ) @@ -250,7 +250,7 @@ def test_get_event_table(self): '"ethertype":"IPv4",' '"portRangeMax":2001,"portRangeMin":2000,"protocol":"tcp",' '"remoteGroupId":null,"remoteIp":null,"ruleId":"800"}]}' - ), + ), indent=4, sort_keys=True ) @@ -266,7 +266,7 @@ def test_get_event_table(self): '"networkComponentId":"100",' '"networkInterfaceType":"public",' '"requestId":"6b9a87a9ab8ac9a22e87a00"}' - ), + ), indent=4, sort_keys=True ) @@ -283,7 +283,7 @@ def test_get_event_table(self): '"ethertype":"IPv4",' '"portRangeMax":2001,"portRangeMin":2000,"protocol":"tcp",' '"remoteGroupId":null,"remoteIp":null,"ruleId":"800"}]}' - ), + ), indent=4, sort_keys=True ) @@ -299,7 +299,7 @@ def test_get_event_table(self): '"networkComponentId":"100",' '"networkInterfaceType":"public",' '"requestId":"4709e02ad42c83f80345904"}' - ), + ), indent=4, sort_keys=True ) @@ -308,7 +308,7 @@ def test_get_event_table(self): for log in expected: table_fix.add_row([log['event'], log['object'], log['type'], log['date'], - log['username'], log['metadata'].strip("{}\n\t")]) + log['username'], log['metadata'].strip("{}\n\t")]) expected_output = formatting.format_output(table_fix) + '\n' result = self.run_command(args=['event-log', 'get', '--metadata'], fmt='table') diff --git a/tests/CLI/modules/securitygroup_tests.py b/tests/CLI/modules/securitygroup_tests.py index 4ce0cd564..b6801fcc8 100644 --- a/tests/CLI/modules/securitygroup_tests.py +++ b/tests/CLI/modules/securitygroup_tests.py @@ -310,7 +310,7 @@ def test_securitygroup_get_by_request_id(self, event_mock): '"requestId": "96c9b47b9e102d2e1d81fba",' '"securityGroupId": "200",' '"securityGroupName": "test_SG"}' - ), + ), indent=4, sort_keys=True ) @@ -329,7 +329,7 @@ def test_securitygroup_get_by_request_id(self, event_mock): '"remoteGroupId": null,' '"remoteIp": null,' '"ruleId": "800"}]}' - ), + ), indent=4, sort_keys=True ) diff --git a/tests/CLI/modules/vs/vs_create_tests.py b/tests/CLI/modules/vs/vs_create_tests.py index 5075d225e..91946471a 100644 --- a/tests/CLI/modules/vs/vs_create_tests.py +++ b/tests/CLI/modules/vs/vs_create_tests.py @@ -604,12 +604,12 @@ def test_create_with_userdata(self, confirm_mock): '--userdata', 'This is my user data ok']) self.assert_no_fail(result) expected_guest = [ - { - 'domain': 'test.local', - 'hostname': 'test', - 'userData': [{'value': 'This is my user data ok'}] - } - ] + { + 'domain': 'test.local', + 'hostname': 'test', + 'userData': [{'value': 'This is my user data ok'}] + } + ] # Returns a list of API calls that hit SL_Product_Order::placeOrder api_call = self.calls('SoftLayer_Product_Order', 'placeOrder') # Doing this because the placeOrder args are huge and mostly not needed to test diff --git a/tests/managers/account_tests.py b/tests/managers/account_tests.py new file mode 100644 index 000000000..7efc42acd --- /dev/null +++ b/tests/managers/account_tests.py @@ -0,0 +1,54 @@ +""" + SoftLayer.tests.managers.account_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +""" + +from SoftLayer.managers.account import AccountManager as AccountManager +from SoftLayer import testing + + +class AccountManagerTests(testing.TestCase): + + def set_up(self): + self.manager = AccountManager(self.client) + self.SLNOE = 'SoftLayer_Notification_Occurrence_Event' + + def test_get_summary(self): + self.manager.get_summary() + self.assert_called_with('SoftLayer_Account', 'getObject') + + def test_get_upcoming_events(self): + self.manager.get_upcoming_events() + self.assert_called_with(self.SLNOE, 'getAllObjects') + + def test_ack_event(self): + self.manager.ack_event(12345) + self.assert_called_with(self.SLNOE, 'acknowledgeNotification', identifier=12345) + + def test_get_event(self): + self.manager.get_event(12345) + self.assert_called_with(self.SLNOE, 'getObject', identifier=12345) + + def test_get_invoices(self): + self.manager.get_invoices() + self.assert_called_with('SoftLayer_Account', 'getInvoices') + + def test_get_invoices_closed(self): + self.manager.get_invoices(closed=True) + _filter = { + 'invoices': { + 'createDate': { + 'operation': 'orderBy', + 'options': [{ + 'name': 'sort', + 'value': ['DESC'] + }] + } + } + } + self.assert_called_with('SoftLayer_Account', 'getInvoices', filter=_filter) + + def test_get_billing_items(self): + self.manager.get_billing_items(12345) + self.assert_called_with('SoftLayer_Billing_Invoice', 'getInvoiceTopLevelItems')