Repository: softlayer/softlayer-python

Issue:
Firmware Update
Please add the ability to the API and CLI to update the server's firmware.

http://sldn.softlayer.com/reference/services/SoftLayer_Hardware_Server/createFirmwareUpdateTransaction

Discussion:

Patch:
diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py
index 67675738..507ce234 100644
--- a/SoftLayer/CLI/routes.py
+++ b/SoftLayer/CLI/routes.py
@@ -149,6 +149,7 @@
     ('server:reboot', 'SoftLayer.CLI.server.power:reboot'),
     ('server:reload', 'SoftLayer.CLI.server.reload:cli'),
     ('server:credentials', 'SoftLayer.CLI.server.credentials:cli'),
+    ('server:update-firmware', 'SoftLayer.CLI.server.update_firmware:cli'),
 
     ('snapshot', 'SoftLayer.CLI.snapshot'),
     ('snapshot:cancel', 'SoftLayer.CLI.snapshot.cancel:cli'),
diff --git a/SoftLayer/CLI/server/update_firmware.py b/SoftLayer/CLI/server/update_firmware.py
new file mode 100644
index 00000000..f5782787
--- /dev/null
+++ b/SoftLayer/CLI/server/update_firmware.py
@@ -0,0 +1,27 @@
+"""Update firmware."""
+# :license: MIT, see LICENSE for more details.
+
+import SoftLayer
+from SoftLayer.CLI import environment
+from SoftLayer.CLI import exceptions
+from SoftLayer.CLI import formatting
+from SoftLayer.CLI import helpers
+
+import click
+
+
+@click.command()
+@click.argument('identifier')
+@environment.pass_env
+def cli(env, identifier):
+    """Update server firmware."""
+
+    mgr = SoftLayer.HardwareManager(env.client)
+    hw_id = helpers.resolve_id(mgr.resolve_ids, identifier, 'hardware')
+    if env.skip_confirmations or formatting.confirm('This will power off the '
+                                                    'server with id %s and '
+                                                    'update device firmware. '
+                                                    'Continue?' % hw_id):
+        mgr.update_firmware(hw_id)
+    else:
+        raise exceptions.CLIAbort('Aborted.')
diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py
index 29dde1b1..90e911f5 100644
--- a/SoftLayer/managers/hardware.py
+++ b/SoftLayer/managers/hardware.py
@@ -745,6 +745,28 @@ def edit(self, hardware_id, userdata=None, hostname=None, domain=None,
 
         return self.hardware.editObject(obj, id=hardware_id)
 
+    def update_firmware(self,
+                        hardware_id,
+                        ipmi=True,
+                        raid_controller=True,
+                        bios=True,
+                        hard_drive=True):
+        """Update hardware firmware.
+
+        This will cause the server to be unavailable for ~20 minutes.
+
+        :param int hardware_id: The ID of the hardware to have its firmware
+                                updated.
+        :param bool ipmi: Update the ipmi firmware.
+        :param bool raid_controller: Update the raid controller firmware.
+        :param bool bios: Update the bios firmware.
+        :param bool hard_drive: Update the hard drive firmware.
+        """
+
+        return self.hardware.createFirmwareUpdateTransaction(
+            bool(ipmi), bool(raid_controller), bool(bios), bool(hard_drive),
+            id=hardware_id)
+
 
 def get_default_value(package_options, category, hourly=False):
     """Returns the default price ID for the specified category.
diff --git a/SoftLayer/transports.py b/SoftLayer/transports.py
index 15964e5c..86307354 100644
--- a/SoftLayer/transports.py
+++ b/SoftLayer/transports.py
@@ -226,8 +226,8 @@ def __call__(self, call):
             module_path = 'SoftLayer.testing.fixtures.%s' % call.service
             module = importlib.import_module(module_path)
         except ImportError:
-            raise NotImplementedError('%s::%s fixture is not implemented'
-                                      % (call.service, call.method))
+            raise NotImplementedError('%s fixture is not implemented'
+                                      % call.service)
         try:
             return getattr(module, call.method)
         except AttributeError:


Test patch:
diff --git a/SoftLayer/testing/fixtures/SoftLayer_Hardware_Server.py b/SoftLayer/testing/fixtures/SoftLayer_Hardware_Server.py
index 6da486e1..f8828daa 100644
--- a/SoftLayer/testing/fixtures/SoftLayer_Hardware_Server.py
+++ b/SoftLayer/testing/fixtures/SoftLayer_Hardware_Server.py
@@ -70,6 +70,7 @@
 rebootSoft = True
 rebootDefault = True
 rebootHard = True
+createFirmwareUpdateTransaction = True
 setUserMetadata = ['meta']
 reloadOperatingSystem = 'OK'
 getReverseDomainRecords = [
diff --git a/SoftLayer/tests/CLI/modules/server_tests.py b/SoftLayer/tests/CLI/modules/server_tests.py
index f11b58a6..2152df0c 100644
--- a/SoftLayer/tests/CLI/modules/server_tests.py
+++ b/SoftLayer/tests/CLI/modules/server_tests.py
@@ -636,3 +636,14 @@ def test_get_default_value_returns_none_for_unknown_category(self):
         option_mock = {'categories': {'cat1': []}}
         output = create._get_default_value(option_mock, 'nope')
         self.assertEqual(None, output)
+
+    @mock.patch('SoftLayer.CLI.formatting.confirm')
+    def test_update_firmware(self, confirm_mock):
+        confirm_mock.return_value = True
+        result = self.run_command(['server', 'update-firmware', '1000'])
+
+        self.assertEqual(result.exit_code, 0)
+        self.assertEqual(result.output, "")
+        self.assert_called_with('SoftLayer_Hardware_Server',
+                                'createFirmwareUpdateTransaction',
+                                args=((1, 1, 1, 1)), identifier=1000)
diff --git a/SoftLayer/tests/managers/hardware_tests.py b/SoftLayer/tests/managers/hardware_tests.py
index 86ea0719..5044e247 100644
--- a/SoftLayer/tests/managers/hardware_tests.py
+++ b/SoftLayer/tests/managers/hardware_tests.py
@@ -473,10 +473,27 @@ def test_edit(self):
                                 identifier=100)
 
     def test_rescue(self):
-        # Test rescue environment
-        restult = self.hardware.rescue(1234)
+        result = self.hardware.rescue(1234)
 
-        self.assertEqual(restult, True)
+        self.assertEqual(result, True)
         self.assert_called_with('SoftLayer_Hardware_Server',
                                 'bootToRescueLayer',
                                 identifier=1234)
+
+    def test_update_firmware(self):
+        result = self.hardware.update_firmware(100)
+
+        self.assertEqual(result, True)
+        self.assert_called_with('SoftLayer_Hardware_Server',
+                                'createFirmwareUpdateTransaction',
+                                identifier=100, args=(1, 1, 1, 1))
+
+    def test_update_firmware_selective(self):
+        result = self.hardware.update_firmware(100,
+                                               ipmi=False,
+                                               hard_drive=False)
+
+        self.assertEqual(result, True)
+        self.assert_called_with('SoftLayer_Hardware_Server',
+                                'createFirmwareUpdateTransaction',
+                                identifier=100, args=(0, 1, 1, 0))