diff --git a/SoftLayer/CLI/order/place.py b/SoftLayer/CLI/order/place.py index 1e21e544a..6d51ab935 100644 --- a/SoftLayer/CLI/order/place.py +++ b/SoftLayer/CLI/order/place.py @@ -43,7 +43,7 @@ def cli(env, package_keyname, location, preset, verify, billing, complex_type, can then be converted to be made programmatically by calling SoftLayer.OrderingManager.place_order() with the same keynames. - Packages for ordering can be retrived from `slcli order package-list` + Packages for ordering can be retrieved from `slcli order package-list` Presets for ordering can be retrieved from `slcli order preset-list` (not all packages have presets) diff --git a/SoftLayer/CLI/order/place_quote.py b/SoftLayer/CLI/order/place_quote.py new file mode 100644 index 000000000..3f5215c40 --- /dev/null +++ b/SoftLayer/CLI/order/place_quote.py @@ -0,0 +1,91 @@ +"""Place quote""" +# :license: MIT, see LICENSE for more details. + +import json + +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers import ordering + + +@click.command() +@click.argument('package_keyname') +@click.argument('location') +@click.option('--preset', + help="The order preset (if required by the package)") +@click.option('--name', + help="A custom name to be assigned to the quote (optional)") +@click.option('--send-email', + is_flag=True, + help="The quote will be sent to the email address associated.") +@click.option('--complex-type', help=("The complex type of the order. This typically begins" + " with 'SoftLayer_Container_Product_Order_'.")) +@click.option('--extras', + help="JSON string denoting extra data that needs to be sent with the order") +@click.argument('order_items', nargs=-1) +@environment.pass_env +def cli(env, package_keyname, location, preset, name, send_email, complex_type, + extras, order_items): + """Place a quote. + + This CLI command is used for placing a quote of the specified package in + the given location (denoted by a datacenter's long name). Orders made via the CLI + can then be converted to be made programmatically by calling + SoftLayer.OrderingManager.place_quote() with the same keynames. + + Packages for ordering can be retrieved from `slcli order package-list` + Presets for ordering can be retrieved from `slcli order preset-list` (not all packages + have presets) + + Items can be retrieved from `slcli order item-list`. In order to find required + items for the order, use `slcli order category-list`, and then provide the + --category option for each category code in `slcli order item-list`. + + \b + Example: + # Place quote a VSI with 4 CPU, 16 GB RAM, 100 GB SAN disk, + # Ubuntu 16.04, and 1 Gbps public & private uplink in dal13 + slcli order place-quote --name "foobar" --send-email CLOUD_SERVER DALLAS13 \\ + GUEST_CORES_4 \\ + RAM_16_GB \\ + REBOOT_REMOTE_CONSOLE \\ + 1_GBPS_PUBLIC_PRIVATE_NETWORK_UPLINKS \\ + BANDWIDTH_0_GB_2 \\ + 1_IP_ADDRESS \\ + GUEST_DISK_100_GB_SAN \\ + OS_UBUNTU_16_04_LTS_XENIAL_XERUS_MINIMAL_64_BIT_FOR_VSI \\ + MONITORING_HOST_PING \\ + NOTIFICATION_EMAIL_AND_TICKET \\ + AUTOMATED_NOTIFICATION \\ + UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT \\ + NESSUS_VULNERABILITY_ASSESSMENT_REPORTING \\ + --extras '{"virtualGuests": [{"hostname": "test", "domain": "softlayer.com"}]}' \\ + --complex-type SoftLayer_Container_Product_Order_Virtual_Guest + + """ + manager = ordering.OrderingManager(env.client) + + if extras: + extras = json.loads(extras) + + args = (package_keyname, location, order_items) + kwargs = {'preset_keyname': preset, + 'extras': extras, + 'quantity': 1, + 'quote_name': name, + 'send_email': send_email, + 'complex_type': complex_type} + + order = manager.place_quote(*args, **kwargs) + + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + table.add_row(['id', order['quote']['id']]) + table.add_row(['name', order['quote']['name']]) + table.add_row(['created', order['orderDate']]) + table.add_row(['expires', order['quote']['expirationDate']]) + table.add_row(['status', order['quote']['status']]) + env.fout(table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 196616a8e..b1c9fb0e2 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -210,6 +210,7 @@ ('order:place', 'SoftLayer.CLI.order.place:cli'), ('order:preset-list', 'SoftLayer.CLI.order.preset_list:cli'), ('order:package-locations', 'SoftLayer.CLI.order.package_locations:cli'), + ('order:place-quote', 'SoftLayer.CLI.order.place_quote:cli'), ('rwhois', 'SoftLayer.CLI.rwhois'), ('rwhois:edit', 'SoftLayer.CLI.rwhois.edit:cli'), diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index 9b1fadec0..3677ecedd 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -430,6 +430,40 @@ def place_order(self, package_keyname, location, item_keynames, complex_type=Non extras=extras, quantity=quantity) return self.order_svc.placeOrder(order) + def place_quote(self, package_keyname, location, item_keynames, complex_type=None, + preset_keyname=None, extras=None, quantity=1, quote_name=None, send_email=False): + + """Place a quote with the given package and prices. + + This function takes in parameters needed for an order and places the quote. + + :param str package_keyname: The keyname for the package being ordered + :param str location: The datacenter location string for ordering (Ex: DALLAS13) + :param list item_keynames: The list of item keyname strings to order. To see list of + possible keynames for a package, use list_items() + (or `slcli order item-list`) + :param str complex_type: The complex type to send with the order. Typically begins + with `SoftLayer_Container_Product_Order_`. + :param string preset_keyname: If needed, specifies a preset to use for that package. + To see a list of possible keynames for a package, use + list_preset() (or `slcli order preset-list`) + :param dict extras: The extra data for the order in dictionary format. + Example: A VSI order requires hostname and domain to be set, so + extras will look like the following: + {'virtualGuests': [{'hostname': 'test', domain': 'softlayer.com'}]} + :param int quantity: The number of resources to order + :param string quote_name: A custom name to be assigned to the quote (optional). + :param bool send_email: This flag indicates that the quote should be sent to the email + address associated with the account or order. + """ + order = self.generate_order(package_keyname, location, item_keynames, complex_type=complex_type, + hourly=False, preset_keyname=preset_keyname, extras=extras, quantity=quantity) + + order['quoteName'] = quote_name + order['sendQuoteEmailFlag'] = send_email + + return self.order_svc.placeQuote(order) + def generate_order(self, package_keyname, location, item_keynames, complex_type=None, hourly=True, preset_keyname=None, extras=None, quantity=1): """Generates an order with the given package and prices. diff --git a/tests/CLI/modules/order_tests.py b/tests/CLI/modules/order_tests.py index b48cb8a53..ab3f3e2fd 100644 --- a/tests/CLI/modules/order_tests.py +++ b/tests/CLI/modules/order_tests.py @@ -114,6 +114,35 @@ def test_place(self): 'status': 'APPROVED'}, json.loads(result.output)) + def test_place_quote(self): + order_date = '2018-04-04 07:39:20' + expiration_date = '2018-05-04 07:39:20' + quote_name = 'foobar' + order = {'orderDate': order_date, + 'quote': { + 'id': 1234, + 'name': quote_name, + 'expirationDate': expiration_date, + 'status': 'PENDING' + }} + place_quote_mock = self.set_mock('SoftLayer_Product_Order', 'placeQuote') + items_mock = self.set_mock('SoftLayer_Product_Package', 'getItems') + + place_quote_mock.return_value = order + items_mock.return_value = self._get_order_items() + + result = self.run_command(['order', 'place-quote', '--name', 'foobar', 'package', 'DALLAS13', + 'ITEM1', '--complex-type', 'SoftLayer_Container_Product_Order_Thing']) + + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Product_Order', 'placeQuote') + self.assertEqual({'id': 1234, + 'name': quote_name, + 'created': order_date, + 'expires': expiration_date, + 'status': 'PENDING'}, + json.loads(result.output)) + def test_verify_hourly(self): order_date = '2017-04-04 07:39:20' order = {'orderId': 1234, 'orderDate': order_date, diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index 729659ba6..5149f6ed8 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -431,6 +431,33 @@ def test_place_order(self): extras=extras, quantity=quantity) self.assertEqual(ord_mock.return_value, order) + def test_place_quote(self): + ord_mock = self.set_mock('SoftLayer_Product_Order', 'placeQuote') + ord_mock.return_value = {'id': 1234} + pkg = 'PACKAGE_KEYNAME' + location = 'DALLAS13' + items = ['ITEM1', 'ITEM2'] + hourly = False + preset_keyname = 'PRESET' + complex_type = 'Complex_Type' + extras = {'foo': 'bar'} + quantity = 1 + name = 'wombat' + send_email = True + + with mock.patch.object(self.ordering, 'generate_order') as gen_mock: + gen_mock.return_value = {'order': {}} + + order = self.ordering.place_quote(pkg, location, items, preset_keyname=preset_keyname, + complex_type=complex_type, extras=extras, quantity=quantity, + quote_name=name, send_email=send_email) + + gen_mock.assert_called_once_with(pkg, location, items, hourly=hourly, + preset_keyname=preset_keyname, + complex_type=complex_type, + extras=extras, quantity=quantity) + self.assertEqual(ord_mock.return_value, order) + def test_locations(self): locations = self.ordering.package_locations('BARE_METAL_CPU') self.assertEqual('WASHINGTON07', locations[0]['keyname'])