Repository: softlayer/softlayer-python
Pull request #715 (2016-04-04 05:15:28)

Add ability to attach/detach devices to tickets.
Add attach arguments for hardware and virtual to ticket create CLI.
Add new ticket attach and detach CLI commands.
Add methods to ticket manager to attach and detach devices.

Addresses #667 


Patch:
diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py
index 0d93f2ec..26378fb4 100644
--- a/SoftLayer/CLI/routes.py
+++ b/SoftLayer/CLI/routes.py
@@ -188,6 +188,8 @@
     ('ticket:update', 'SoftLayer.CLI.ticket.update:cli'),
     ('ticket:subjects', 'SoftLayer.CLI.ticket.subjects:cli'),
     ('ticket:summary', 'SoftLayer.CLI.ticket.summary:cli'),
+    ('ticket:attach', 'SoftLayer.CLI.ticket.attach:cli'),
+    ('ticket:detach', 'SoftLayer.CLI.ticket.detach:cli'),
 
     ('vlan', 'SoftLayer.CLI.vlan'),
     ('vlan:detail', 'SoftLayer.CLI.vlan.detail:cli'),
diff --git a/SoftLayer/CLI/ticket/attach.py b/SoftLayer/CLI/ticket/attach.py
new file mode 100644
index 00000000..98adaa65
--- /dev/null
+++ b/SoftLayer/CLI/ticket/attach.py
@@ -0,0 +1,42 @@
+"""Attach devices to a ticket."""
+# :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()
+@click.argument('identifier', type=int)
+@click.option('--hardware',
+              'hardware_identifier',
+              help="The identifier for hardware to attach")
+@click.option('--virtual',
+              'virtual_identifier',
+              help="The identifier for a virtual server to attach")
+@environment.pass_env
+def cli(env, identifier, hardware_identifier, virtual_identifier):
+    """Attach devices to a ticket."""
+    ticket_mgr = SoftLayer.TicketManager(env.client)
+
+    if hardware_identifier and virtual_identifier:
+        raise exceptions.ArgumentError(
+            "Cannot attach hardware and a virtual server at the same time")
+    elif hardware_identifier:
+        hardware_mgr = SoftLayer.HardwareManager(env.client)
+        hardware_id = helpers.resolve_id(hardware_mgr.resolve_ids,
+                                         hardware_identifier,
+                                         'hardware')
+        ticket_mgr.attach_hardware(identifier, hardware_id)
+    elif virtual_identifier:
+        vs_mgr = SoftLayer.VSManager(env.client)
+        vs_id = helpers.resolve_id(vs_mgr.resolve_ids,
+                                   virtual_identifier,
+                                   'VS')
+        ticket_mgr.attach_virtual_server(identifier, vs_id)
+    else:
+        raise exceptions.ArgumentError(
+            "Must have a hardware or virtual server identifier to attach")
diff --git a/SoftLayer/CLI/ticket/create.py b/SoftLayer/CLI/ticket/create.py
index 82f81ee8..71ebd26e 100644
--- a/SoftLayer/CLI/ticket/create.py
+++ b/SoftLayer/CLI/ticket/create.py
@@ -5,25 +5,49 @@
 
 import SoftLayer
 from SoftLayer.CLI import environment
+from SoftLayer.CLI import helpers
 from SoftLayer.CLI import ticket
 
 
 @click.command()
 @click.option('--title', required=True, help="The title of the ticket")
 @click.option('--subject-id',
+              type=int,
               required=True,
               help="""The subject id to use for the ticket,
  issue 'slcli ticket subjects' to get the list""")
 @click.option('--body', help="The ticket body")
+@click.option('--hardware',
+              'hardware_identifier',
+              help="The identifier for hardware to attach")
+@click.option('--virtual',
+              'virtual_identifier',
+              help="The identifier for a virtual server to attach")
 @environment.pass_env
-def cli(env, title, subject_id, body):
+def cli(env, title, subject_id, body, hardware_identifier, virtual_identifier):
     """Create a support ticket."""
-    mgr = SoftLayer.TicketManager(env.client)
+    ticket_mgr = SoftLayer.TicketManager(env.client)
 
     if body is None:
         body = click.edit('\n\n' + ticket.TEMPLATE_MSG)
 
-    created_ticket = mgr.create_ticket(title=title,
-                                       body=body,
-                                       subject=subject_id)
-    env.fout(ticket.get_ticket_results(mgr, created_ticket['id']))
+    created_ticket = ticket_mgr.create_ticket(
+        title=title,
+        body=body,
+        subject=subject_id)
+
+    if hardware_identifier:
+        hardware_mgr = SoftLayer.HardwareManager(env.client)
+        hardware_id = helpers.resolve_id(hardware_mgr.resolve_ids,
+                                         hardware_identifier,
+                                         'hardware')
+        ticket_mgr.attach_hardware(created_ticket['id'], hardware_id)
+
+    if virtual_identifier:
+        vs_mgr = SoftLayer.VSManager(env.client)
+        vs_id = helpers.resolve_id(vs_mgr.resolve_ids,
+                                   virtual_identifier,
+                                   'VS')
+        ticket_mgr.attach_virtual_server(created_ticket['id'], vs_id)
+
+    env.fout(ticket.get_ticket_results(ticket_mgr, created_ticket['id']))
diff --git a/SoftLayer/CLI/ticket/detach.py b/SoftLayer/CLI/ticket/detach.py
new file mode 100644
index 00000000..8c8cae05
--- /dev/null
+++ b/SoftLayer/CLI/ticket/detach.py
@@ -0,0 +1,42 @@
+"""Detach devices from a ticket."""
+# :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()
+@click.argument('identifier', type=int)
+@click.option('--hardware',
+              'hardware_identifier',
+              help="The identifier for hardware to detach")
+@click.option('--virtual',
+              'virtual_identifier',
+              help="The identifier for a virtual server to detach")
+@environment.pass_env
+def cli(env, identifier, hardware_identifier, virtual_identifier):
+    """Detach devices from a ticket."""
+    ticket_mgr = SoftLayer.TicketManager(env.client)
+
+    if hardware_identifier and virtual_identifier:
+        raise exceptions.ArgumentError(
+            "Cannot detach hardware and a virtual server at the same time")
+    elif hardware_identifier:
+        hardware_mgr = SoftLayer.HardwareManager(env.client)
+        hardware_id = helpers.resolve_id(hardware_mgr.resolve_ids,
+                                         hardware_identifier,
+                                         'hardware')
+        ticket_mgr.detach_hardware(identifier, hardware_id)
+    elif virtual_identifier:
+        vs_mgr = SoftLayer.VSManager(env.client)
+        vs_id = helpers.resolve_id(vs_mgr.resolve_ids,
+                                   virtual_identifier,
+                                   'VS')
+        ticket_mgr.detach_virtual_server(identifier, vs_id)
+    else:
+        raise exceptions.ArgumentError(
+            "Must have a hardware or virtual server identifier to detach")
diff --git a/SoftLayer/fixtures/SoftLayer_Ticket.py b/SoftLayer/fixtures/SoftLayer_Ticket.py
index 4933cc62..c85deda8 100644
--- a/SoftLayer/fixtures/SoftLayer_Ticket.py
+++ b/SoftLayer/fixtures/SoftLayer_Ticket.py
@@ -33,3 +33,20 @@
 }
 edit = True
 addUpdate = {}
+
+addAttachedHardware = {
+    "id": 123,
+    "createDate": "2013-08-01T14:14:04-07:00",
+    "hardwareId": 1,
+    "ticketId": 100
+}
+
+addAttachedVirtualGuest = {
+    "id": 123,
+    "createDate": "2013-08-01T14:14:04-07:00",
+    "virtualGuestId": 1,
+    "ticketId": 100
+}
+
+removeAttachedHardware = True
+removeAttachedVirtualGuest = True
diff --git a/SoftLayer/managers/ticket.py b/SoftLayer/managers/ticket.py
index dd1be176..eb5a12a6 100644
--- a/SoftLayer/managers/ticket.py
+++ b/SoftLayer/managers/ticket.py
@@ -39,13 +39,13 @@ def list_tickets(self, open_status=True, closed_status=True):
         return self.client.call('Account', call, mask=mask)
 
     def list_subjects(self):
-        """List all tickets."""
+        """List all ticket subjects."""
         return self.client['Ticket_Subject'].getAllObjects()
 
     def get_ticket(self, ticket_id):
         """Get details about a ticket.
 
-        :param integer id: the ticket ID
+        :param integer ticket_id: the ticket ID
         :returns: A dictionary containing a large amount of information about
                   the specified ticket.
 
@@ -79,3 +79,39 @@ def update_ticket(self, ticket_id=None, body=None):
         :param string body: entry to update in the ticket
         """
         return self.ticket.addUpdate({'entry': body}, id=ticket_id)
+
+    def attach_hardware(self, ticket_id=None, hardware_id=None):
+        """Attach hardware to a ticket.
+
+        :param integer ticket_id: the id of the ticket to attach to
+        :param integer hardware_id: the id of the hardware to attach
+        :returns The new ticket attachment
+        """
+        return self.ticket.addAttachedHardware(hardware_id, id=ticket_id)
+
+    def attach_virtual_server(self, ticket_id=None, virtual_id=None):
+        """Attach a virtual server to a ticket.
+
+        :param integer ticket_id: the id of the ticket to attach to
+        :param integer virtual_id: the id of the virtual server to attach
+        :returns The new ticket attachment
+        """
+        return self.ticket.addAttachedVirtualGuest(virtual_id, id=ticket_id)
+
+    def detach_hardware(self, ticket_id=None, hardware_id=None):
+        """Detach hardware from a ticket.
+
+        :param ticket_id: the id of the ticket to detach from
+        :param hardware_id: the id of the hardware to detach
+        :return: Whether the detachment was successful
+        """
+        return self.ticket.removeAttachedHardware(hardware_id, id=ticket_id)
+
+    def detach_virtual_server(self, ticket_id=None, virtual_id=None):
+        """Detach a virtual server from a ticket.
+
+        :param ticket_id: the id of the ticket to detach from
+        :param virtual_id: the id of the virtual server to detach
+        :return: Whether the detachment was successful
+        """
+        return self.ticket.removeAttachedVirtualGuest(virtual_id, id=ticket_id)


Test patch:
diff --git a/tests/CLI/modules/ticket_tests.py b/tests/CLI/modules/ticket_tests.py
index 5a579c64..6f54e5cc 100644
--- a/tests/CLI/modules/ticket_tests.py
+++ b/tests/CLI/modules/ticket_tests.py
@@ -4,9 +4,11 @@
 
     :license: MIT, see LICENSE for more details.
 """
-from SoftLayer import testing
-
 import json
+import mock
+
+from SoftLayer.CLI import exceptions
+from SoftLayer import testing
 
 
 class TicketTests(testing.TestCase):
@@ -38,3 +40,130 @@ def test_detail(self):
         }
         self.assertEqual(result.exit_code, 0)
         self.assertEqual(json.loads(result.output), expected)
+
+    def test_create(self):
+        result = self.run_command(['ticket', 'create', '--title=Test',
+                                   '--subject-id=1000',
+                                   '--body=ticket body'])
+
+        self.assertEqual(result.exit_code, 0)
+
+        args = ({'subjectId': 1000,
+                 'contents': 'ticket body',
+                 'assignedUserId': 12345,
+                 'title': 'Test'}, 'ticket body')
+
+        self.assert_called_with('SoftLayer_Ticket', 'createStandardTicket',
+                                args=args)
+
+    def test_create_and_attach(self):
+        result = self.run_command(['ticket', 'create', '--title=Test',
+                                   '--subject-id=1000',
+                                   '--body=ticket body',
+                                   '--hardware=234',
+                                   '--virtual=567'])
+
+        self.assertEqual(result.exit_code, 0)
+
+        args = ({'subjectId': 1000,
+                 'contents': 'ticket body',
+                 'assignedUserId': 12345,
+                 'title': 'Test'}, 'ticket body')
+
+        self.assert_called_with('SoftLayer_Ticket', 'createStandardTicket',
+                                args=args)
+        self.assert_called_with('SoftLayer_Ticket', 'addAttachedHardware',
+                                args=(234,),
+                                identifier=100)
+        self.assert_called_with('SoftLayer_Ticket', 'addAttachedVirtualGuest',
+                                args=(567,),
+                                identifier=100)
+
+    @mock.patch('click.edit')
+    def test_create_no_body(self, edit_mock):
+        edit_mock.return_value = 'ticket body'
+        result = self.run_command(['ticket', 'create', '--title=Test',
+                                   '--subject-id=1000'])
+        self.assertEqual(result.exit_code, 0)
+
+        args = ({'subjectId': 1000,
+                 'contents': 'ticket body',
+                 'assignedUserId': 12345,
+                 'title': 'Test'}, 'ticket body')
+
+        self.assert_called_with('SoftLayer_Ticket', 'createStandardTicket',
+                                args=args)
+
+    def test_subjects(self):
+        list_expected_ids = [1001, 1002, 1003, 1004, 1005]
+        result = self.run_command(['ticket', 'subjects'])
+
+        self.assertEqual(result.exit_code, 0)
+        results = json.loads(result.output)
+        for result in results:
+            self.assertIn(result['id'], list_expected_ids)
+
+    def test_attach_no_identifier(self):
+        result = self.run_command(['ticket', 'attach', '1'])
+
+        self.assertEqual(result.exit_code, 2)
+        self.assertIsInstance(result.exception, exceptions.ArgumentError)
+
+    def test_attach_two_identifiers(self):
+        result = self.run_command(['ticket',
+                                   'attach',
+                                   '1',
+                                   '--hardware=100',
+                                   '--virtual=100'])
+
+        self.assertEqual(result.exit_code, 2)
+        self.assertIsInstance(result.exception, exceptions.ArgumentError)
+
+    def test_ticket_attach_hardware(self):
+        result = self.run_command(['ticket', 'attach', '1', '--hardware=100'])
+
+        self.assertEqual(result.exit_code, 0)
+        self.assert_called_with('SoftLayer_Ticket', 'addAttachedHardware',
+                                args=(100,),
+                                identifier=1)
+
+    def test_ticket_attach_virtual_server(self):
+        result = self.run_command(['ticket', 'attach', '1', '--virtual=100'])
+
+        self.assertEqual(result.exit_code, 0)
+        self.assert_called_with('SoftLayer_Ticket', 'addAttachedVirtualGuest',
+                                args=(100,),
+                                identifier=1)
+
+    def test_detach_no_identifier(self):
+        result = self.run_command(['ticket', 'detach', '1'])
+
+        self.assertEqual(result.exit_code, 2)
+        self.assertIsInstance(result.exception, exceptions.ArgumentError)
+
+    def test_detach_two_identifiers(self):
+        result = self.run_command(['ticket',
+                                   'detach',
+                                   '1',
+                                   '--hardware=100',
+                                   '--virtual=100'])
+        self.assertEqual(result.exit_code, 2)
+        self.assertIsInstance(result.exception, exceptions.ArgumentError)
+
+    def test_ticket_detach_hardware(self):
+        result = self.run_command(['ticket', 'detach', '1', '--hardware=100'])
+
+        self.assertEqual(result.exit_code, 0)
+        self.assert_called_with('SoftLayer_Ticket',
+                                'removeAttachedHardware',
+                                args=(100,),
+                                identifier=1)
+
+    def test_ticket_detach_virtual_server(self):
+        result = self.run_command(['ticket', 'detach', '1', '--virtual=100'])
+
+        self.assertEqual(result.exit_code, 0)
+        self.assert_called_with('SoftLayer_Ticket',
+                                'removeAttachedVirtualGuest',
+                                args=(100,),
+                                identifier=1)
diff --git a/tests/managers/ticket_tests.py b/tests/managers/ticket_tests.py
index aa75c982..2ebbfced 100644
--- a/tests/managers/ticket_tests.py
+++ b/tests/managers/ticket_tests.py
@@ -77,3 +77,28 @@ def test_update_ticket(self):
         self.assert_called_with('SoftLayer_Ticket', 'addUpdate',
                                 args=({'entry': 'Update1'},),
                                 identifier=100)
+
+    def test_attach_hardware(self):
+        self.ticket.attach_hardware(100, 123)
+        self.assert_called_with('SoftLayer_Ticket', 'addAttachedHardware',
+                                args=(123,),
+                                identifier=100)
+
+    def test_attach_virtual_server(self):
+        self.ticket.attach_virtual_server(100, 123)
+        self.assert_called_with('SoftLayer_Ticket', 'addAttachedVirtualGuest',
+                                args=(123,),
+                                identifier=100)
+
+    def test_detach_hardware(self):
+        self.ticket.detach_hardware(100, 123)
+        self.assert_called_with('SoftLayer_Ticket', 'removeAttachedHardware',
+                                args=(123,),
+                                identifier=100)
+
+    def test_detach_virtual_server(self):
+        self.ticket.detach_virtual_server(100, 123)
+        self.assert_called_with('SoftLayer_Ticket',
+                                'removeAttachedVirtualGuest',
+                                args=(123,),
+                                identifier=100)