Repository: softlayer/softlayer-python
Pull request #1919 (2023-04-19 20:49:53)

New Command: `slcli user apikey`
Related to: https://github.com/softlayer/softlayer-python/issues/1906
Issue: https://github.com/softlayer/softlayer-python/issues/1913
Observations: the new command and its unit tests were added
![image](https://user-images.githubusercontent.com/78806346/233196813-612d034c-c78c-477d-85e9-92a0de66f37a.png)



Patch:
diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py
index 9550b927..104d8d45 100644
--- a/SoftLayer/CLI/routes.py
+++ b/SoftLayer/CLI/routes.py
@@ -389,6 +389,7 @@
     ('user:remove-access', 'SoftLayer.CLI.user.remove_access:cli'),
     ('user:grant-access', 'SoftLayer.CLI.user.grant_access:cli'),
     ('user:vpn-password', 'SoftLayer.CLI.user.vpn_password:cli'),
+    ('user:apikey', 'SoftLayer.CLI.user.apikey:cli'),
 
     ('vlan', 'SoftLayer.CLI.vlan'),
     ('vlan:create', 'SoftLayer.CLI.vlan.create:cli'),
diff --git a/SoftLayer/CLI/user/apikey.py b/SoftLayer/CLI/user/apikey.py
new file mode 100644
index 00000000..c94643d6
--- /dev/null
+++ b/SoftLayer/CLI/user/apikey.py
@@ -0,0 +1,47 @@
+"""Allows to create, remove or refresh user's API authentication key."""
+import click
+
+import SoftLayer
+from SoftLayer.CLI import environment
+from SoftLayer.CLI import exceptions
+from SoftLayer.CLI import helpers
+
+
+@click.command(cls=SoftLayer.CLI.command.SLCommand, )
+@click.argument('identifier')
+@click.option('--add', is_flag=True, default=False,
+              help="Create an user's API authentication key.")
+@click.option('--remove', is_flag=True, default=False,
+              help="Remove an user's API authentication key.")
+@click.option('--refresh', is_flag=True, default=False,
+              help="Refresh an user's API authentication key.")
+@environment.pass_env
+def cli(env, identifier, add, remove, refresh):
+
+    """Allows to create, remove or refresh user's API authentication key.
+
+    Each user can only have a single API key.
+    """
+
+    options = {
+        '--add': add,
+        '--remove': remove,
+        '--refresh': refresh,
+    }
+
+    if not any(options.values()):
+        raise exceptions.CLIAbort(f'At least one option is required: [{",".join(options.keys())}]')
+
+    if sum(options.values()) > 1:
+        raise exceptions.CLIAbort(f'Can only specify one option of [{",".join(options.keys())}]')
+
+    mgr = SoftLayer.UserManager(env.client)
+    user_id = helpers.resolve_id(mgr.resolve_ids, identifier, 'username')
+
+    if remove or refresh:
+        mgr.remove_api_authentication_key(user_id)
+        click.secho('Successfully removed API authentication key', fg='green')
+
+    if add or refresh:
+        api_authentication_key = mgr.add_api_authentication_key(user_id)
+        click.secho(f'Successfully added. New API Authentication Key: {api_authentication_key}', fg='green')
diff --git a/SoftLayer/fixtures/SoftLayer_User_Customer.py b/SoftLayer/fixtures/SoftLayer_User_Customer.py
index 7bd3de7d..b53ea5e9 100644
--- a/SoftLayer/fixtures/SoftLayer_User_Customer.py
+++ b/SoftLayer/fixtures/SoftLayer_User_Customer.py
@@ -84,7 +84,8 @@
 removeBulkPortalPermission = True
 createObject = getObject
 editObject = True
-addApiAuthenticationKey = True
+addApiAuthenticationKey = "1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a"
+removeApiAuthenticationKey = True
 updateVpnUser = True
 removeDedicatedHostAccess = True
 removeHardwareAccess = True
@@ -94,6 +95,17 @@
 addVirtualGuestAccess = True
 updateVpnPassword = True
 
+getApiAuthenticationKeys = [
+    {
+        "authenticationKey": "1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a",
+        "id": 123456,
+        "timestampKey": 12121212,
+        "userId": 111111
+    }
+]
+
+getEmptyApiAuthenticationKeys = []
+
 getHardware = [{
         "domain": "testedit.com",
         "fullyQualifiedDomainName": "test.testedit.com",
diff --git a/SoftLayer/managers/user.py b/SoftLayer/managers/user.py
index b0d85fcd..dfec39bb 100644
--- a/SoftLayer/managers/user.py
+++ b/SoftLayer/managers/user.py
@@ -332,6 +332,26 @@ def add_api_authentication_key(self, user_id):
         """
         return self.user_service.addApiAuthenticationKey(id=user_id)
 
+    def get_api_authentication_keys(self, user_id):
+        """Calls SoftLayer_User_Customer::getApiAuthenticationKeys
+
+        :param int user_id: User to add API key to
+        """
+        return self.user_service.getApiAuthenticationKeys(id=user_id)
+
+    def remove_api_authentication_key(self, user_id):
+        """Calls SoftLayer_User_Customer::getApiAuthenticationKeys and
+
+        SoftLayer_User_Customer::removeApiAuthenticationKey
+
+        :param int user_id: User to remove API key
+        """
+        api_authentication_keys = self.get_api_authentication_keys(user_id)
+        if len(api_authentication_keys) == 0:
+            return True
+
+        return self.user_service.removeApiAuthenticationKey(api_authentication_keys[0]['id'])
+
     def vpn_manual(self, user_id, value):
         """Enable or disable the manual config of subnets.
 
diff --git a/docs/cli/users.rst b/docs/cli/users.rst
index c1f2d79e..e3d45bb1 100644
--- a/docs/cli/users.rst
+++ b/docs/cli/users.rst
@@ -64,3 +64,7 @@ Version 5.6.0 introduces the ability to interact with user accounts from the cli
     :prog: user vpn-password
     :show-nested:
 
+.. click:: SoftLayer.CLI.user.apikey:cli
+    :prog: user apikey
+    :show-nested:
+


Test patch:
diff --git a/tests/CLI/modules/sshkey_tests.py b/tests/CLI/modules/sshkey_tests.py
index c4646f7b..85817590 100644
--- a/tests/CLI/modules/sshkey_tests.py
+++ b/tests/CLI/modules/sshkey_tests.py
@@ -118,7 +118,7 @@ def test_print_key(self):
                          {'id': 1234, 'label': 'label', 'notes': 'notes'})
 
     def test_print_key_file(self):
-        if (sys.platform.startswith("win")):
+        if sys.platform.startswith("win"):
             self.skipTest("Test doesn't work in Windows")
         with tempfile.NamedTemporaryFile() as sshkey_file:
             service = self.client['Security_Ssh_Key']
diff --git a/tests/CLI/modules/user_tests.py b/tests/CLI/modules/user_tests.py
index f863da2f..02956e68 100644
--- a/tests/CLI/modules/user_tests.py
+++ b/tests/CLI/modules/user_tests.py
@@ -10,6 +10,7 @@
 
 from unittest import mock as mock
 
+from SoftLayer.fixtures import SoftLayer_User_Customer
 from SoftLayer import testing
 
 
@@ -387,3 +388,31 @@ def test_remove_without_password(self):
         result = self.run_command(['user', 'vpn-password', '123456'])
         self.assertEqual(2, result.exit_code)
         self.assertIn("Missing option '--password'", result.output)
+
+    def test_api_key_without_option(self):
+        result = self.run_command(['user', 'apikey', '123456'])
+        self.assertEqual(2, result.exit_code)
+        self.assertIn('At least one option is required', result.exception.message)
+
+    def test_api_key_with_all_option(self):
+        result = self.run_command(['user', 'apikey', '123456', '--add', '--remove', '--refresh'])
+        self.assertEqual(2, result.exit_code)
+        self.assertIn('Can only specify one option', result.exception.message)
+
+    def test_remove_api_authentication_key_without_api_key(self):
+        mock = self.set_mock('SoftLayer_User_Customer', 'getApiAuthenticationKeys')
+        mock.return_value = SoftLayer_User_Customer.getEmptyApiAuthenticationKeys
+        result = self.run_command(['user', 'apikey', '123456', '--remove'])
+        self.assert_no_fail(result)
+
+    def test_add_api_authentication_key(self):
+        result = self.run_command(['user', 'apikey', '123456', '--add'])
+        self.assert_no_fail(result)
+
+    def test_remove_api_authentication_key(self):
+        result = self.run_command(['user', 'apikey', '123456', '--remove'])
+        self.assert_no_fail(result)
+
+    def test_refresh_api_authentication_key(self):
+        result = self.run_command(['user', 'apikey', '123456', '--refresh'])
+        self.assert_no_fail(result)
diff --git a/tests/CLI/modules/vs/vs_create_tests.py b/tests/CLI/modules/vs/vs_create_tests.py
index e70446a4..01f58481 100644
--- a/tests/CLI/modules/vs/vs_create_tests.py
+++ b/tests/CLI/modules/vs/vs_create_tests.py
@@ -693,7 +693,7 @@ def test_create_vs_no_confirm(self, confirm_mock):
         self.assertEqual(result.exit_code, 2)
 
     def test_create_vs_export(self):
-        if (sys.platform.startswith("win")):
+        if sys.platform.startswith("win"):
             self.skipTest("Test doesn't work in Windows")
         with tempfile.NamedTemporaryFile() as config_file:
             result = self.run_command(['vs', 'create', '--hostname', 'TEST', '--export', config_file.name,
diff --git a/tests/managers/user_tests.py b/tests/managers/user_tests.py
index 0fca5e2f..2d50d820 100644
--- a/tests/managers/user_tests.py
+++ b/tests/managers/user_tests.py
@@ -334,3 +334,15 @@ def test_remove_dedicated(self):
     def test_update_vpn_password(self):
         self.manager.update_vpn_password(123456, "Mypassword1.")
         self.assert_called_with('SoftLayer_User_Customer', 'updateVpnPassword')
+
+    def test_add_api_authentication_key(self):
+        self.manager.add_api_authentication_key(123456)
+        self.assert_called_with('SoftLayer_User_Customer', 'addApiAuthenticationKey')
+
+    def test_get_api_authentication_keys(self):
+        self.manager.get_api_authentication_keys(123456)
+        self.assert_called_with('SoftLayer_User_Customer', 'getApiAuthenticationKeys')
+
+    def test_remove_api_authentication_key(self):
+        self.manager.remove_api_authentication_key(123456)
+        self.assert_called_with('SoftLayer_User_Customer', 'removeApiAuthenticationKey')