From 016471dd544bdce61dd9e58603f4ca4287e8e4a1 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 27 May 2022 16:09:12 -0500 Subject: [PATCH 001/710] fixing doc build issues --- docs/cli/reports.rst | 2 ++ docs/requirements.txt | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/cli/reports.rst b/docs/cli/reports.rst index 39299e99b..377533b1d 100644 --- a/docs/cli/reports.rst +++ b/docs/cli/reports.rst @@ -9,6 +9,7 @@ There are a few report type commands in the SLCLI. :prog: summary :show-nested: + A list of datacenters, and how many servers, VSI, vlans, subnets and public_ips are in each. @@ -21,6 +22,7 @@ A list of datacenters, and how many servers, VSI, vlans, subnets and public_ips :prog: report datacenter-closures :show-nested: + Displays some basic information about the Servers and other resources that are in Datacenters scheduled to be decommissioned in the near future. See `IBM Cloud Datacenter Consolidation `_ for diff --git a/docs/requirements.txt b/docs/requirements.txt index acb2b7258..18540d3db 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,4 +1,5 @@ sphinx sphinx-click click -prettytable \ No newline at end of file +prettytable +rich \ No newline at end of file From 2b62252b8b323e0ebfe794ebeaa2a88d0681ac29 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 27 May 2022 17:32:50 -0500 Subject: [PATCH 002/710] islcli groundwork --- SoftLayer/API.py | 154 ++++++++++++++++++++++++++++++++++++++++ SoftLayer/CLI/login.py | 19 +++++ SoftLayer/CLI/routes.py | 1 + SoftLayer/auth.py | 22 ++++++ 4 files changed, 196 insertions(+) create mode 100644 SoftLayer/CLI/login.py diff --git a/SoftLayer/API.py b/SoftLayer/API.py index 21f21ffc6..7477a4c27 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -27,6 +27,7 @@ __all__ = [ 'create_client_from_env', + 'employee_client', 'Client', 'BaseClient', 'API_PUBLIC_ENDPOINT', @@ -142,6 +143,88 @@ def create_client_from_env(username=None, return BaseClient(auth=auth, transport=transport, config_file=config_file) +def employee_client(username=None, + api_key=None, + endpoint_url=None, + timeout=None, + auth=None, + config_file=None, + proxy=None, + user_agent=None, + transport=None, + verify=True): + """Creates an INTERNAL SoftLayer API client using your environment. + + Settings are loaded via keyword arguments, environemtal variables and + config file. + + :param username: your user ID + :param api_key: hash from SoftLayer_User_Employee::performExternalAuthentication(username, password, 2fa_string) + :param endpoint_url: the API endpoint base URL you wish to connect to. + Set this to API_PRIVATE_ENDPOINT to connect via SoftLayer's private + network. + :param proxy: proxy to be used to make API calls + :param integer timeout: timeout for API requests + :param auth: an object which responds to get_headers() to be inserted into + the xml-rpc headers. Example: `BasicAuthentication` + :param config_file: A path to a configuration file used to load settings + :param user_agent: an optional User Agent to report when making API + calls if you wish to bypass the packages built in User Agent string + :param transport: An object that's callable with this signature: + transport(SoftLayer.transports.Request) + :param bool verify: decide to verify the server's SSL/TLS cert. DO NOT SET + TO FALSE WITHOUT UNDERSTANDING THE IMPLICATIONS. + + Usage: + + >>> import SoftLayer + >>> client = SoftLayer.create_client_from_env() + >>> resp = client.call('Account', 'getObject') + >>> resp['companyName'] + 'Your Company' + + """ + settings = config.get_client_settings(username=username, + api_key=api_key, + endpoint_url=endpoint_url, + timeout=timeout, + proxy=proxy, + verify=verify, + config_file=config_file) + + if transport is None: + url = settings.get('endpoint_url') + if url is not None and '/rest' in url: + # If this looks like a rest endpoint, use the rest transport + transport = transports.RestTransport( + endpoint_url=settings.get('endpoint_url'), + proxy=settings.get('proxy'), + timeout=settings.get('timeout'), + user_agent=user_agent, + verify=verify, + ) + else: + # Default the transport to use XMLRPC + transport = transports.XmlRpcTransport( + endpoint_url=settings.get('endpoint_url'), + proxy=settings.get('proxy'), + timeout=settings.get('timeout'), + user_agent=user_agent, + verify=verify, + ) + + + if auth is None and settings.get('username') and settings.get('api_key'): + real_transport = getattr(transport, 'transport', transport) + if isinstance(real_transport, transports.XmlRpcTransport): + auth = slauth.EmployeeAuthentication( + settings.get('username'), + settings.get('api_key'), + ) + + return BaseClient(auth=auth, transport=transport) + + def Client(**kwargs): """Get a SoftLayer API Client using environmental settings. @@ -545,6 +628,77 @@ def __repr__(self): return "IAMClient(transport=%r, auth=%r)" % (self.transport, self.auth) +class EmployeeClient(BaseClient): + """Internal SoftLayer Client + + :param auth: auth driver that looks like SoftLayer.auth.AuthenticationBase + :param transport: An object that's callable with this signature: transport(SoftLayer.transports.Request) + """ + + def authenticate_with_password(self, username, password, security_token=None): + """Performs IBM IAM Username/Password Authentication + + :param string username: your softlayer username + :param string password: your softlayer password + :param int security_token: your 2FA token, prompt if None + """ + + self.auth = None + if security_token is None: + security_token = input("Enter your 2FA Token now: ") + if len(security_token) != 6: + raise Exception("Invalid security token: {}".format(security_token)) + + auth_result = self.call('SoftLayer_User_Employee', 'performExternalAuthentication', + username, password, security_token) + + + self.settings['softlayer']['access_token'] = auth_result['hash'] + self.settings['softlayer']['userId'] = auth_result['userId'] + # self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] + + config.write_config(self.settings, self.config_file) + self.auth = slauth.EmployeeAuthentication(auth_result['userId'], auth_result['hash']) + + return auth_result + + + + def authenticate_with_hash(self, userId, access_token): + """Authenticates to the Internal SL API with an employee userid + token + + :param string userId: Employee UserId + :param string access_token: Employee Hash Token + """ + self.auth = slauth.EmployeeAuthentication(userId, access_token) + + def refresh_token(self, userId, auth_token): + """Refreshes the login token""" + + self.auth = None + auth_result = self.call('SoftLayer_User_Employee', 'refreshEncryptedToken', auth_token, id=userId) + print(auth_result) + self.settings['softlayer']['access_token'] = auth_result[0] + + config.write_config(self.settings, self.config_file) + self.auth = slauth.EmployeeAuthentication(userId, auth_result[0]) + return auth_result + + def call(self, service, method, *args, **kwargs): + """Handles refreshing IAM tokens in case of a HTTP 401 error""" + try: + return super().call(service, method, *args, **kwargs) + except exceptions.SoftLayerAPIError as ex: + + if ex.faultCode == 401: + LOGGER.warning("Token has expired, trying to refresh. %s", ex.faultString) + return ex + else: + raise ex + + def __repr__(self): + return "IAMClient(transport=%r, auth=%r)" % (self.transport, self.auth) + class Service(object): """A SoftLayer Service. diff --git a/SoftLayer/CLI/login.py b/SoftLayer/CLI/login.py new file mode 100644 index 000000000..9d27b3615 --- /dev/null +++ b/SoftLayer/CLI/login.py @@ -0,0 +1,19 @@ +"""Login with your employee username, password, 2fa token""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.CLI.command import SLCommand as SLCommand +from SoftLayer.CLI import config +from SoftLayer.CLI import environment + + +@click.command(cls=SLCommand) +@environment.pass_env +def cli(env): + """Logs you into the internal SoftLayer Network. + + username and password can be set in SL_USER and SL_PASSWORD env variables. You will be prompted for them otherwise. + """ + + print("OK") diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 603d5a1d2..f0029697e 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -8,6 +8,7 @@ ALL_ROUTES = [ ('shell', 'SoftLayer.shell.core:cli'), + ('login', 'SoftLayer.CLI.login:cli'), ('call-api', 'SoftLayer.CLI.call_api:cli'), diff --git a/SoftLayer/auth.py b/SoftLayer/auth.py index 18e0ebe96..e3caf1e54 100644 --- a/SoftLayer/auth.py +++ b/SoftLayer/auth.py @@ -12,6 +12,7 @@ 'TokenAuthentication', 'BasicHTTPAuthentication', 'AuthenticationBase', + 'EmployeeAuthentication' ] @@ -137,3 +138,24 @@ def get_request(self, request): def __repr__(self): return "BearerAuthentication(username={}, token={})".format(self.username, self.api_key) + +class EmployeeAuthentication(AuthenticationBase): + """Token-based authentication class. + + :param username str: a user's username + :param api_key str: a user's API key + """ + def __init__(self, user_id, user_hash): + self.user_id = user_id + self.hash = user_hash + + def get_request(self, request): + """Sets token-based auth headers.""" + request.headers['employeesession'] = { + 'userId': self.user_id, + 'authToken': self.hash, + } + return request + + def __repr__(self): + return "EmployeeAuthentication(userId=%r,hash=%s)" % (self.user_id, self.hash) \ No newline at end of file From a25cfdc00ab1fba7ebfbc9a20ba448191efc54f3 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 31 May 2022 17:04:17 -0500 Subject: [PATCH 003/710] Getting employee login command working --- SoftLayer/API.py | 39 +++++++++++++++++++++++------------- SoftLayer/CLI/environment.py | 2 +- SoftLayer/CLI/login.py | 37 ++++++++++++++++++++++++++++++++-- SoftLayer/auth.py | 2 +- SoftLayer/consts.py | 1 + 5 files changed, 63 insertions(+), 18 deletions(-) diff --git a/SoftLayer/API.py b/SoftLayer/API.py index 7477a4c27..a52645692 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -6,6 +6,7 @@ :license: MIT, see LICENSE for more details. """ # pylint: disable=invalid-name +import os import time import warnings @@ -144,7 +145,8 @@ def create_client_from_env(username=None, def employee_client(username=None, - api_key=None, + access_token=None, + password=None, endpoint_url=None, timeout=None, auth=None, @@ -159,7 +161,8 @@ def employee_client(username=None, config file. :param username: your user ID - :param api_key: hash from SoftLayer_User_Employee::performExternalAuthentication(username, password, 2fa_string) + :param access_token: hash from SoftLayer_User_Employee::performExternalAuthentication(username, password, 2fa_string) + :param password: password to use for employee authentication :param endpoint_url: the API endpoint base URL you wish to connect to. Set this to API_PRIVATE_ENDPOINT to connect via SoftLayer's private network. @@ -185,15 +188,21 @@ def employee_client(username=None, """ settings = config.get_client_settings(username=username, - api_key=api_key, + api_key=None, endpoint_url=endpoint_url, timeout=timeout, proxy=proxy, verify=verify, config_file=config_file) + url = settings.get('endpoint_url') or consts.API_EMPLOYEE_ENDPOINT + + if 'internal' not in url: + raise exceptions.SoftLayerError("{} does not look like an Internal Employee url. Try {}".format( + url, consts.API_EMPLOYEE_ENDPOINT)) + if transport is None: - url = settings.get('endpoint_url') + if url is not None and '/rest' in url: # If this looks like a rest endpoint, use the rest transport transport = transports.RestTransport( @@ -214,15 +223,18 @@ def employee_client(username=None, ) - if auth is None and settings.get('username') and settings.get('api_key'): - real_transport = getattr(transport, 'transport', transport) - if isinstance(real_transport, transports.XmlRpcTransport): - auth = slauth.EmployeeAuthentication( - settings.get('username'), - settings.get('api_key'), - ) + if access_token is None: + access_token = settings.get('access_token') + + user_id = settings.get('user_id') + + # Assume access_token is valid for now, user has logged in before at least. + if access_token and user_id: + auth = slauth.EmployeeAuthentication(user_id, access_token) + return EmployeeClient(auth=auth, transport=transport) + else: + return EmployeeClient(auth=None, transport=transport) - return BaseClient(auth=auth, transport=transport) def Client(**kwargs): @@ -230,8 +242,7 @@ def Client(**kwargs): Deprecated in favor of create_client_from_env() """ - warnings.warn("use SoftLayer.create_client_from_env() instead", - DeprecationWarning) + warnings.warn("use SoftLayer.create_client_from_env() instead", DeprecationWarning) return create_client_from_env(**kwargs) diff --git a/SoftLayer/CLI/environment.py b/SoftLayer/CLI/environment.py index 5744b7595..014ff33b2 100644 --- a/SoftLayer/CLI/environment.py +++ b/SoftLayer/CLI/environment.py @@ -198,7 +198,7 @@ def ensure_client(self, config_file=None, is_demo=False, proxy=None): ) else: # Create SL Client - client = SoftLayer.create_client_from_env( + client = SoftLayer.employee_client( proxy=proxy, config_file=config_file, ) diff --git a/SoftLayer/CLI/login.py b/SoftLayer/CLI/login.py index 9d27b3615..5e7a5c162 100644 --- a/SoftLayer/CLI/login.py +++ b/SoftLayer/CLI/login.py @@ -2,18 +2,51 @@ # :license: MIT, see LICENSE for more details. import click +import os + +from SoftLayer.API import EmployeeClient from SoftLayer.CLI.command import SLCommand as SLCommand -from SoftLayer.CLI import config +from SoftLayer import config from SoftLayer.CLI import environment +# def get_username(env): +# """Gets the username from config or env""" +# settings = + +def censor_password(value): + if value: + value = '*' * len(value) + return value + @click.command(cls=SLCommand) @environment.pass_env def cli(env): """Logs you into the internal SoftLayer Network. - username and password can be set in SL_USER and SL_PASSWORD env variables. You will be prompted for them otherwise. + username: Set this in either the softlayer config, or SL_USER ENV variable + password: Set this in SL_PASSWORD env variable. You will be prompted for them otherwise. """ + settings = config.get_config(config_file=env.config_file) + username = settings.get('username') or os.environ.get('SLCLI_USER') + password = os.environ.get('SLCLI_PASSWORD', '') + yubi = 123456 + + url = settings.get('endpoint_url') or consts.API_EMPLOYEE_ENDPOINT + click.echo("URL: {}".format(url)) + if username is None: + username = input("Username: ") + click.echo("Username: {}".format(username)) + if password is None: + password = env.getpass("Password: ") + click.echo("Password: {}".format(censor_password(password))) + # yubi = input("Yubi: ") + + client = EmployeeClient(config_file=env.config_file) + try: + client.authenticate_with_password(username, password, yubi) + except Exception as e: + click.echo("EXCEPTION: {}".format(e)) print("OK") diff --git a/SoftLayer/auth.py b/SoftLayer/auth.py index e3caf1e54..8811c77d2 100644 --- a/SoftLayer/auth.py +++ b/SoftLayer/auth.py @@ -143,7 +143,7 @@ class EmployeeAuthentication(AuthenticationBase): """Token-based authentication class. :param username str: a user's username - :param api_key str: a user's API key + :param user_hash str: a user's Authentication hash """ def __init__(self, user_id, user_hash): self.user_id = user_id diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 32ebd4ec4..5ac84b099 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -10,5 +10,6 @@ API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' API_PRIVATE_ENDPOINT_REST = 'https://api.service.softlayer.com/rest/v3.1/' +API_EMPLOYEE_ENDPOINT = 'https://internal.app0lb.dal10.softlayer.local/v3/internal/xmlrpc/' USER_AGENT = "softlayer-python/%s" % VERSION CONFIG_FILE = "~/.softlayer" From e8d4d162565dc0dd2ce83dae0eeab0a3a86350d7 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Tue, 31 May 2022 17:42:33 -0500 Subject: [PATCH 004/710] proof of concept for refresh Token --- SoftLayer/API.py | 4 ++-- SoftLayer/CLI/login.py | 39 +++++++++++++++++++++++++++++++++------ islcli | 10 ++++++++++ 3 files changed, 45 insertions(+), 8 deletions(-) create mode 100755 islcli diff --git a/SoftLayer/API.py b/SoftLayer/API.py index a52645692..6256116a6 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -665,7 +665,7 @@ def authenticate_with_password(self, username, password, security_token=None): self.settings['softlayer']['access_token'] = auth_result['hash'] - self.settings['softlayer']['userId'] = auth_result['userId'] + self.settings['softlayer']['userId'] = str(auth_result['userId']) # self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] config.write_config(self.settings, self.config_file) @@ -708,7 +708,7 @@ def call(self, service, method, *args, **kwargs): raise ex def __repr__(self): - return "IAMClient(transport=%r, auth=%r)" % (self.transport, self.auth) + return "EmployeeClient(transport=%r, auth=%r)" % (self.transport, self.auth) class Service(object): """A SoftLayer Service. diff --git a/SoftLayer/CLI/login.py b/SoftLayer/CLI/login.py index 5e7a5c162..34bd03030 100644 --- a/SoftLayer/CLI/login.py +++ b/SoftLayer/CLI/login.py @@ -28,10 +28,36 @@ def cli(env): username: Set this in either the softlayer config, or SL_USER ENV variable password: Set this in SL_PASSWORD env variable. You will be prompted for them otherwise. """ - settings = config.get_config(config_file=env.config_file) - username = settings.get('username') or os.environ.get('SLCLI_USER') + config_settings = config.get_config(config_file=env.config_file) + settings = config_settings['softlayer'] + username = settings.get('username') or os.environ.get('SLCLI_USER', None) password = os.environ.get('SLCLI_PASSWORD', '') - yubi = 123456 + yubi = None +# client = EmployeeClient(config_file=env.config_file) + client = env.client + + # Might already be logged in, try and refresh token + if settings.get('access_token') and settings.get('userid'): + client.authenticate_with_hash(settings.get('userid'), settings.get('access_token')) + try: + employee = client.call('SoftLayer_User_Employee', 'getObject', id=settings.get('userid'), mask="mask[id,username]") + print(employee) + refresh = client.call('SoftLayer_User_Employee', 'refreshEncryptedToken', settings.get('access_token'), id=settings.get('userid')) + print("REFRESH:\n{}\n".format(refresh)) + # we expect 2 results, a hash and a timeout + if len(refresh) > 1: + for returned_data in refresh: + # Access tokens should be 188 characters, but just incase. + if len(returned_data) > 180: + settings['access_token'] = returned_data + else: + raise Exception("Got unexpected data. Expected 2 properties: {}".format(refresh)) + config_settings['softlayer'] = settings + config.write_config(config_settings, env.config_file) + return + except Exception as ex: + print("Error with Hash, try with password: {}".format(ex)) + url = settings.get('endpoint_url') or consts.API_EMPLOYEE_ENDPOINT click.echo("URL: {}".format(url)) @@ -41,11 +67,12 @@ def cli(env): if password is None: password = env.getpass("Password: ") click.echo("Password: {}".format(censor_password(password))) - # yubi = input("Yubi: ") + yubi = input("Yubi: ") - client = EmployeeClient(config_file=env.config_file) + try: - client.authenticate_with_password(username, password, yubi) + result = client.authenticate_with_password(username, password, str(yubi)) + print(result) except Exception as e: click.echo("EXCEPTION: {}".format(e)) diff --git a/islcli b/islcli new file mode 100755 index 000000000..7822bc2bb --- /dev/null +++ b/islcli @@ -0,0 +1,10 @@ +#!python +import re +import sys + +from SoftLayer.CLI.core import main + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + # print("arvs[0] = %s" % sys.argv[0]) + sys.exit(main()) From 65df1cd75bb8c3ebf76ef19eff62ba260a4ce653 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 1 Jun 2022 17:19:49 -0500 Subject: [PATCH 005/710] building up unit tests --- SoftLayer/API.py | 5 +++++ tests/api_tests.py | 47 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/SoftLayer/API.py b/SoftLayer/API.py index 6256116a6..bf510cf21 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -704,6 +704,11 @@ def call(self, service, method, *args, **kwargs): if ex.faultCode == 401: LOGGER.warning("Token has expired, trying to refresh. %s", ex.faultString) return ex + if ex.faultCode == "SoftLayer_Exception_EncryptedToken_Expired": + userId = self.settings['softlayer'].get('userId') + access_token = self.settings['softlayer'].get('access_token') + LOGGER.warning("Token has expired2, trying to refresh. %s", ex.faultString) + self.refresh_token() else: raise ex diff --git a/tests/api_tests.py b/tests/api_tests.py index ea4726a6e..b42001656 100644 --- a/tests/api_tests.py +++ b/tests/api_tests.py @@ -4,12 +4,15 @@ :license: MIT, see LICENSE for more details. """ +import io from unittest import mock as mock +import requests import SoftLayer import SoftLayer.API from SoftLayer import testing from SoftLayer import transports +from SoftLayer import exceptions class Initialization(testing.TestCase): @@ -310,3 +313,47 @@ def test_authenticate_with_password(self, _call): self.assertIsNotNone(self.client.auth) self.assertEqual(self.client.auth.user_id, 12345) self.assertEqual(self.client.auth.auth_token, 'TOKEN') + + +class EmployeeClientTests(testing.TestCase): + + @staticmethod + def failed_log(): + response = requests.Response() + list_body = b''' + + + + + + faultCode + + SoftLayer_Exception_Public + + + + faultString + + Invalid username/password + + + + + + ''' + response.raw = io.BytesIO(list_body) + response.status_code = 200 + return response + + def set_up(self): + self.client = SoftLayer.API.EmployeeClient(config_file='./tests/testconfig') + + @mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') + def test_auth_with_pass(self, api_response): + api_response.return_value = self.failed_log() + exception = self.assertRaises( + exceptions.SoftLayerAPIError, + self.client.authenticate_with_password, 'testUser', 'testPassword', '123456') + + + self.assertEqual(exception.faultCode, "SoftLayer_Exception_Public") \ No newline at end of file From 96559074cda727d299a2e1dcc23851dab35e6e10 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 2 Jun 2022 15:11:14 -0500 Subject: [PATCH 006/710] expanding employee unit tests --- SoftLayer/API.py | 10 +++- SoftLayer/CLI/login.py | 13 +--- SoftLayer/fixtures/xmlrpc/invalidLogin.xml | 21 +++++++ SoftLayer/fixtures/xmlrpc/refreshFailure.xml | 0 SoftLayer/fixtures/xmlrpc/refreshSuccess.xml | 17 ++++++ SoftLayer/fixtures/xmlrpc/successLogin.xml | 21 +++++++ tests/api_tests.py | 62 ++++++++++++-------- 7 files changed, 106 insertions(+), 38 deletions(-) create mode 100644 SoftLayer/fixtures/xmlrpc/invalidLogin.xml create mode 100644 SoftLayer/fixtures/xmlrpc/refreshFailure.xml create mode 100644 SoftLayer/fixtures/xmlrpc/refreshSuccess.xml create mode 100644 SoftLayer/fixtures/xmlrpc/successLogin.xml diff --git a/SoftLayer/API.py b/SoftLayer/API.py index bf510cf21..f6b76801d 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -688,8 +688,14 @@ def refresh_token(self, userId, auth_token): self.auth = None auth_result = self.call('SoftLayer_User_Employee', 'refreshEncryptedToken', auth_token, id=userId) - print(auth_result) - self.settings['softlayer']['access_token'] = auth_result[0] + if len(auth_result) > 1: + for returned_data in auth_result: + # Access tokens should be 188 characters, but just incase its longer or something. + if len(returned_data) > 180: + self.settings['softlayer']['access_token'] = returned_data + else: + message = "Excepted 2 properties from refreshEncryptedToken, got |{}|".format(auth_result) + raise exceptions.SoftLayerAPIError(message) config.write_config(self.settings, self.config_file) self.auth = slauth.EmployeeAuthentication(userId, auth_result[0]) diff --git a/SoftLayer/CLI/login.py b/SoftLayer/CLI/login.py index 34bd03030..13afe1e8a 100644 --- a/SoftLayer/CLI/login.py +++ b/SoftLayer/CLI/login.py @@ -42,21 +42,14 @@ def cli(env): try: employee = client.call('SoftLayer_User_Employee', 'getObject', id=settings.get('userid'), mask="mask[id,username]") print(employee) + client.refresh_token(settings.get('userid'), settings.get('access_token')) refresh = client.call('SoftLayer_User_Employee', 'refreshEncryptedToken', settings.get('access_token'), id=settings.get('userid')) - print("REFRESH:\n{}\n".format(refresh)) - # we expect 2 results, a hash and a timeout - if len(refresh) > 1: - for returned_data in refresh: - # Access tokens should be 188 characters, but just incase. - if len(returned_data) > 180: - settings['access_token'] = returned_data - else: - raise Exception("Got unexpected data. Expected 2 properties: {}".format(refresh)) + config_settings['softlayer'] = settings config.write_config(config_settings, env.config_file) return except Exception as ex: - print("Error with Hash, try with password: {}".format(ex)) + print("Error with Hash Authentication, try with password: {}".format(ex)) url = settings.get('endpoint_url') or consts.API_EMPLOYEE_ENDPOINT diff --git a/SoftLayer/fixtures/xmlrpc/invalidLogin.xml b/SoftLayer/fixtures/xmlrpc/invalidLogin.xml new file mode 100644 index 000000000..1c993d2b5 --- /dev/null +++ b/SoftLayer/fixtures/xmlrpc/invalidLogin.xml @@ -0,0 +1,21 @@ + + + + + + + faultCode + + SoftLayer_Exception_Public + + + + faultString + + Invalid username/password + + + + + + \ No newline at end of file diff --git a/SoftLayer/fixtures/xmlrpc/refreshFailure.xml b/SoftLayer/fixtures/xmlrpc/refreshFailure.xml new file mode 100644 index 000000000..e69de29bb diff --git a/SoftLayer/fixtures/xmlrpc/refreshSuccess.xml b/SoftLayer/fixtures/xmlrpc/refreshSuccess.xml new file mode 100644 index 000000000..0b8003b30 --- /dev/null +++ b/SoftLayer/fixtures/xmlrpc/refreshSuccess.xml @@ -0,0 +1,17 @@ + + + + + + + + REFRESHEDTOKENaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + + + 300 + + + + + + \ No newline at end of file diff --git a/SoftLayer/fixtures/xmlrpc/successLogin.xml b/SoftLayer/fixtures/xmlrpc/successLogin.xml new file mode 100644 index 000000000..880d9497e --- /dev/null +++ b/SoftLayer/fixtures/xmlrpc/successLogin.xml @@ -0,0 +1,21 @@ + + + + + + + hash + + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + + + + userId + + 1234 + + + + + + diff --git a/tests/api_tests.py b/tests/api_tests.py index b42001656..e46f055c2 100644 --- a/tests/api_tests.py +++ b/tests/api_tests.py @@ -5,6 +5,7 @@ :license: MIT, see LICENSE for more details. """ import io +import os from unittest import mock as mock import requests @@ -317,43 +318,52 @@ def test_authenticate_with_password(self, _call): class EmployeeClientTests(testing.TestCase): + @staticmethod - def failed_log(): + def setup_response(filename, status_code=200, total_items=1): + basepath = os.path.dirname(__file__) + body = b'' + print(f"Base Path: {basepath}") + with open(f"{basepath}/../SoftLayer/fixtures/xmlrpc/{filename}.xml", 'rb') as fixture: + body = fixture.read() response = requests.Response() - list_body = b''' - - - - - - faultCode - - SoftLayer_Exception_Public - - - - faultString - - Invalid username/password - - - - - - ''' + list_body = body response.raw = io.BytesIO(list_body) - response.status_code = 200 + response.headers['SoftLayer-Total-Items'] = total_items + response.status_code = status_code return response + def set_up(self): self.client = SoftLayer.API.EmployeeClient(config_file='./tests/testconfig') @mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') - def test_auth_with_pass(self, api_response): - api_response.return_value = self.failed_log() + def test_auth_with_pass_failure(self, api_response): + api_response.return_value = self.setup_response('invalidLogin') exception = self.assertRaises( exceptions.SoftLayerAPIError, self.client.authenticate_with_password, 'testUser', 'testPassword', '123456') + self.assertEqual(exception.faultCode, "SoftLayer_Exception_Public") + @mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') + def test_auth_with_pass_success(self, api_response): + api_response.return_value = self.setup_response('successLogin') + result = self.client.authenticate_with_password('testUser', 'testPassword', '123456') + print(result) + self.assertEqual(result['userId'], 1234) + self.assertEqual(self.client.settings['softlayer']['userid'], '1234') + self.assertIn('x'*200, self.client.settings['softlayer']['access_token']) + + def test_auth_with_hash(self): + self.client.auth = None + self.client.authenticate_with_hash(5555, 'abcdefg') + self.assertEqual(self.client.auth.user_id, 5555) + self.assertEqual(self.client.auth.hash, 'abcdefg') - self.assertEqual(exception.faultCode, "SoftLayer_Exception_Public") \ No newline at end of file + @mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') + def test_refresh_token(self, api_response): + api_response.return_value = self.setup_response('refreshSuccess') + result = self.client.refresh_token(9999, 'qweasdzxcqweasdzxcqweasdzxc') + self.assertEqual(self.client.auth.user_id, 9999) + self.assertIn('REFRESHEDTOKENaaaa', self.client.auth.hash) + \ No newline at end of file From 70689872a40d85d01b6ded36dc39f46b3368a2be Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 2 Jun 2022 16:36:17 -0500 Subject: [PATCH 007/710] added account flag to islcli --- SoftLayer/API.py | 31 +++++++++---- SoftLayer/CLI/core.py | 8 ++-- .../fixtures/xmlrpc/Employee_getObject.xml | 21 +++++++++ SoftLayer/fixtures/xmlrpc/expiredToken.xml | 21 +++++++++ tests/api_tests.py | 46 ++++++++++++++++++- 5 files changed, 112 insertions(+), 15 deletions(-) create mode 100644 SoftLayer/fixtures/xmlrpc/Employee_getObject.xml create mode 100644 SoftLayer/fixtures/xmlrpc/expiredToken.xml diff --git a/SoftLayer/API.py b/SoftLayer/API.py index f6b76801d..d212daff5 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -646,6 +646,11 @@ class EmployeeClient(BaseClient): :param transport: An object that's callable with this signature: transport(SoftLayer.transports.Request) """ + def __init__(self, auth=None, transport=None, config_file=None, account_id=None): + BaseClient.__init__(self, auth, transport, config_file) + self.account_id = account_id + + def authenticate_with_password(self, username, password, security_token=None): """Performs IBM IAM Username/Password Authentication @@ -687,14 +692,16 @@ def refresh_token(self, userId, auth_token): """Refreshes the login token""" self.auth = None - auth_result = self.call('SoftLayer_User_Employee', 'refreshEncryptedToken', auth_token, id=userId) + + # Go directly to base client, to avoid infite loop if the token is super expired. + auth_result = BaseClient.call(self, 'SoftLayer_User_Employee', 'refreshEncryptedToken', auth_token, id=userId) if len(auth_result) > 1: for returned_data in auth_result: # Access tokens should be 188 characters, but just incase its longer or something. if len(returned_data) > 180: self.settings['softlayer']['access_token'] = returned_data else: - message = "Excepted 2 properties from refreshEncryptedToken, got |{}|".format(auth_result) + message = "Excepted 2 properties from refreshEncryptedToken, got {}|".format(auth_result) raise exceptions.SoftLayerAPIError(message) config.write_config(self.settings, self.config_file) @@ -702,19 +709,23 @@ def refresh_token(self, userId, auth_token): return auth_result def call(self, service, method, *args, **kwargs): - """Handles refreshing IAM tokens in case of a HTTP 401 error""" + """Handles refreshing Employee tokens in case of a HTTP 401 error""" + if (service == 'SoftLayer_Account' or service == 'Account') and not kwargs.get('id'): + if not self.account_id: + raise exceptions.SoftLayerError("SoftLayer_Account service requires an ID") + kwargs['id'] = self.account_id + try: - return super().call(service, method, *args, **kwargs) + return BaseClient.call(self, service, method, *args, **kwargs) except exceptions.SoftLayerAPIError as ex: - - if ex.faultCode == 401: - LOGGER.warning("Token has expired, trying to refresh. %s", ex.faultString) - return ex if ex.faultCode == "SoftLayer_Exception_EncryptedToken_Expired": userId = self.settings['softlayer'].get('userId') access_token = self.settings['softlayer'].get('access_token') - LOGGER.warning("Token has expired2, trying to refresh. %s", ex.faultString) - self.refresh_token() + LOGGER.warning("Token has expired, trying to refresh. %s", ex.faultString) + self.refresh_token(userId, access_token) + # Try the Call again this time.... + return BaseClient.call(self, service, method, *args, **kwargs) + else: raise ex diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index e79fc2880..013796f4d 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -70,9 +70,8 @@ def get_version_message(ctx, param, value): ctx.exit() -@click.group(help="SoftLayer Command-line Client", - epilog="""To use most commands your SoftLayer username and api_key need to be configured. -The easiest way to do that is to use: 'slcli setup'""", +@click.group(help="SoftLayer Employee Command-line Client", + epilog="""Run 'islcli login' to authenticate""", cls=CommandLoader, context_settings=CONTEXT_SETTINGS) @click.option('--format', @@ -103,6 +102,7 @@ def get_version_message(ctx, param, value): help="Use demo data instead of actually making API calls") @click.option('--version', is_flag=True, expose_value=False, is_eager=True, callback=get_version_message, help="Show version information.", allow_from_autoenv=False,) +@click.option('-a', '--account', help="Account Id") @environment.pass_env def cli(env, format='table', @@ -111,6 +111,7 @@ def cli(env, proxy=None, really=False, demo=False, + account_id=None, **kwargs): """Main click CLI entry-point.""" @@ -133,6 +134,7 @@ def cli(env, env.vars['_timings'] = SoftLayer.DebugTransport(env.client.transport) env.vars['verbose'] = verbose env.client.transport = env.vars['_timings'] + env.client.account_id = account_id @cli.result_callback() diff --git a/SoftLayer/fixtures/xmlrpc/Employee_getObject.xml b/SoftLayer/fixtures/xmlrpc/Employee_getObject.xml new file mode 100644 index 000000000..57ec3f140 --- /dev/null +++ b/SoftLayer/fixtures/xmlrpc/Employee_getObject.xml @@ -0,0 +1,21 @@ + + + + + + + id + + 5555 + + + + username + + testUser + + + + + + \ No newline at end of file diff --git a/SoftLayer/fixtures/xmlrpc/expiredToken.xml b/SoftLayer/fixtures/xmlrpc/expiredToken.xml new file mode 100644 index 000000000..43237bb3e --- /dev/null +++ b/SoftLayer/fixtures/xmlrpc/expiredToken.xml @@ -0,0 +1,21 @@ + + + + + + + faultCode + + SoftLayer_Exception_EncryptedToken_Expired + + + + faultString + + The token has expired. + + + + + + \ No newline at end of file diff --git a/tests/api_tests.py b/tests/api_tests.py index e46f055c2..2e1ab0fae 100644 --- a/tests/api_tests.py +++ b/tests/api_tests.py @@ -14,6 +14,7 @@ from SoftLayer import testing from SoftLayer import transports from SoftLayer import exceptions +from SoftLayer import auth as slauth class Initialization(testing.TestCase): @@ -323,7 +324,6 @@ class EmployeeClientTests(testing.TestCase): def setup_response(filename, status_code=200, total_items=1): basepath = os.path.dirname(__file__) body = b'' - print(f"Base Path: {basepath}") with open(f"{basepath}/../SoftLayer/fixtures/xmlrpc/{filename}.xml", 'rb') as fixture: body = fixture.read() response = requests.Response() @@ -366,4 +366,46 @@ def test_refresh_token(self, api_response): result = self.client.refresh_token(9999, 'qweasdzxcqweasdzxcqweasdzxc') self.assertEqual(self.client.auth.user_id, 9999) self.assertIn('REFRESHEDTOKENaaaa', self.client.auth.hash) - \ No newline at end of file + + @mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') + def test_expired_token_is_refreshed(self, api_response): + api_response.side_effect = [ + self.setup_response('expiredToken'), + self.setup_response('refreshSuccess'), + self.setup_response('Employee_getObject') + ] + self.client.auth = slauth.EmployeeAuthentication(5555, 'aabbccee') + self.client.settings['softlayer']['userid'] = '5555' + result = self.client.call('SoftLayer_User_Employee', 'getObject', id=5555) + self.assertIn('REFRESHEDTOKENaaaa', self.client.auth.hash) + self.assertEqual('testUser', result['username']) + + @mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') + def test_expired_token_is_really_expored(self, api_response): + api_response.side_effect = [ + self.setup_response('expiredToken'), + self.setup_response('expiredToken') + ] + self.client.auth = slauth.EmployeeAuthentication(5555, 'aabbccee') + self.client.settings['softlayer']['userid'] = '5555' + exception = self.assertRaises( + exceptions.SoftLayerAPIError, + self.client.call, 'SoftLayer_User_Employee', 'getObject', id=5555) + self.assertEqual(None, self.client.auth) + self.assertEqual(exception.faultCode, "SoftLayer_Exception_EncryptedToken_Expired") + + @mock.patch('SoftLayer.API.BaseClient.call') + def test_account_check(self, _call): + self.client.transport = self.mocks + exception = self.assertRaises( + exceptions.SoftLayerError, + self.client.call, "SoftLayer_Account", "getObject") + self.assertEqual(str(exception), "SoftLayer_Account service requires an ID") + self.client.account_id = 1234 + self.client.call("SoftLayer_Account", "getObject") + self.client.call("SoftLayer_Account", "getObject1", id=9999) + + _call.assert_has_calls([ + mock.call(self.client, 'SoftLayer_Account', 'getObject', id=1234), + mock.call(self.client, 'SoftLayer_Account', 'getObject1', id=9999), + ]) \ No newline at end of file From f7c5146149b76447e13b1333f40b8e6a59a29c85 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 2 Jun 2022 16:47:38 -0500 Subject: [PATCH 008/710] typo fix --- SoftLayer/CLI/core.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index 013796f4d..56bcd37a2 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -102,7 +102,7 @@ def get_version_message(ctx, param, value): help="Use demo data instead of actually making API calls") @click.option('--version', is_flag=True, expose_value=False, is_eager=True, callback=get_version_message, help="Show version information.", allow_from_autoenv=False,) -@click.option('-a', '--account', help="Account Id") +@click.option('--account', '-a', help="Account Id") @environment.pass_env def cli(env, format='table', @@ -111,7 +111,7 @@ def cli(env, proxy=None, really=False, demo=False, - account_id=None, + account=None, **kwargs): """Main click CLI entry-point.""" @@ -134,7 +134,8 @@ def cli(env, env.vars['_timings'] = SoftLayer.DebugTransport(env.client.transport) env.vars['verbose'] = verbose env.client.transport = env.vars['_timings'] - env.client.account_id = account_id + print("Account ID is now: {}".format(account)) + env.client.account_id = account @cli.result_callback() From d8714dd1af3fbeadb8dfbbd9bf6ca90cf63f0151 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 2 Jun 2022 16:58:30 -0500 Subject: [PATCH 009/710] Added config defaults for userid and access_token --- SoftLayer/API.py | 10 +++++----- SoftLayer/config.py | 6 ++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/SoftLayer/API.py b/SoftLayer/API.py index d212daff5..944bb16c1 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -226,13 +226,15 @@ def employee_client(username=None, if access_token is None: access_token = settings.get('access_token') - user_id = settings.get('user_id') + user_id = settings.get('userid') # Assume access_token is valid for now, user has logged in before at least. if access_token and user_id: auth = slauth.EmployeeAuthentication(user_id, access_token) return EmployeeClient(auth=auth, transport=transport) else: + # This is for logging in mostly. + LOGGER.info("No access_token or userid found in settings, creating a No Auth client for now.") return EmployeeClient(auth=None, transport=transport) @@ -670,7 +672,7 @@ def authenticate_with_password(self, username, password, security_token=None): self.settings['softlayer']['access_token'] = auth_result['hash'] - self.settings['softlayer']['userId'] = str(auth_result['userId']) + self.settings['softlayer']['userid'] = str(auth_result['userId']) # self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] config.write_config(self.settings, self.config_file) @@ -691,8 +693,6 @@ def authenticate_with_hash(self, userId, access_token): def refresh_token(self, userId, auth_token): """Refreshes the login token""" - self.auth = None - # Go directly to base client, to avoid infite loop if the token is super expired. auth_result = BaseClient.call(self, 'SoftLayer_User_Employee', 'refreshEncryptedToken', auth_token, id=userId) if len(auth_result) > 1: @@ -719,7 +719,7 @@ def call(self, service, method, *args, **kwargs): return BaseClient.call(self, service, method, *args, **kwargs) except exceptions.SoftLayerAPIError as ex: if ex.faultCode == "SoftLayer_Exception_EncryptedToken_Expired": - userId = self.settings['softlayer'].get('userId') + userId = self.settings['softlayer'].get('userid') access_token = self.settings['softlayer'].get('access_token') LOGGER.warning("Token has expired, trying to refresh. %s", ex.faultString) self.refresh_token(userId, access_token) diff --git a/SoftLayer/config.py b/SoftLayer/config.py index 5ae8c7131..d909d3d49 100644 --- a/SoftLayer/config.py +++ b/SoftLayer/config.py @@ -59,6 +59,8 @@ def get_client_settings_config_file(**kwargs): # pylint: disable=inconsistent-r 'endpoint_url': '', 'timeout': '0', 'proxy': '', + 'userid': '', + 'access_token': '' }) config.read(config_files) @@ -69,6 +71,8 @@ def get_client_settings_config_file(**kwargs): # pylint: disable=inconsistent-r 'proxy': config.get('softlayer', 'proxy'), 'username': config.get('softlayer', 'username'), 'api_key': config.get('softlayer', 'api_key'), + 'userid': config.get('softlayer', 'userid'), + 'access_token': config.get('softlayer', 'access_token'), } @@ -109,6 +113,8 @@ def get_config(config_file=None): config['softlayer']['endpoint_url'] = '' config['softlayer']['api_key'] = '' config['softlayer']['timeout'] = '0' + config['softlayer']['userid'] = '' + config['softlayer']['access_tokne'] = '' return config From ac3b1e09a0ef0e88fdd2689acf71cea0212476d7 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 7 Jun 2022 11:26:55 -0500 Subject: [PATCH 010/710] updating readme and setup.py --- README.rst | 112 +++++++++++++++++------------------------ SoftLayer/CLI/login.py | 1 - setup.py | 15 +++--- 3 files changed, 52 insertions(+), 76 deletions(-) diff --git a/README.rst b/README.rst index 3cb2219f8..af53ab3e9 100644 --- a/README.rst +++ b/README.rst @@ -1,43 +1,16 @@ SoftLayer API Python Client =========================== -.. image:: https://github.com/softlayer/softlayer-python/workflows/Tests/badge.svg - :target: https://github.com/softlayer/softlayer-python/actions?query=workflow%3ATests -.. image:: https://github.com/softlayer/softlayer-python/workflows/documentation/badge.svg - :target: https://github.com/softlayer/softlayer-python/actions?query=workflow%3Adocumentation +This library is provided `as is` to make internal IMS API calls. You are responsible for your API usage, and any abuse, intentional or accidental, will result in your employee account being locked or limited. -.. image:: https://landscape.io/github/softlayer/softlayer-python/master/landscape.svg - :target: https://landscape.io/github/softlayer/softlayer-python/master - -.. image:: https://badge.fury.io/py/SoftLayer.svg - :target: http://badge.fury.io/py/SoftLayer - -.. image:: https://coveralls.io/repos/github/softlayer/softlayer-python/badge.svg?branch=master - :target: https://coveralls.io/github/softlayer/softlayer-python?branch=master - -.. image:: https://snapcraft.io//slcli/badge.svg - :target: https://snapcraft.io/slcli - - -This library provides a simple Python client to interact with `SoftLayer's -XML-RPC API `_. - -A command-line interface is also included and can be used to manage various -SoftLayer products and services. +Make sure you use the HTTPS url `https://internal.app0lb.dal10.softlayer.local/v3/internal/xmlrpc/` Documentation ------------- -Documentation for the Python client is available at `Read the Docs `_ . +DThis project is based off the `SLCLI `_ , and most things that work there will work here. -Additional API documentation can be found on the SoftLayer Development Network: - -* `SoftLayer API reference - `_ -* `Object mask information and examples - `_ -* `Code Examples - `_ +There is no internal API documentation like SLDN. Installation ------------ @@ -45,41 +18,36 @@ Install via pip: .. code-block:: bash - $ pip install softlayer - - -Or you can install from source. Download source and run: + $ git clone https://github.ibm.com/SoftLayer/internal-softlayer-cli + $ cd internal-softlayer-cli + $ python setup.py install + $ ./islcli login -.. code-block:: bash - $ python setup.py install +Configuration +------------- -Another (safer) method of installation is to use the published snap. Snaps are available for any Linux OS running snapd, the service that runs and manage snaps. Snaps are "auto-updating" packages and will not disrupt the current versions of libraries and software packages on your Linux-based system. To learn more, please visit: https://snapcraft.io/ +The config file is located at `~/.softlayer` or `~/AppData/Roaming/softlayer` for Windows systems. -To install the slcli snap: +Your config file should look something like this for using the islcli. Beware the `islcli` and `slcli` use the same config for the moment. You need to set `verify = False` in the config because the internal endpoint uses a self-signed SSL certificate. .. code-block:: bash - - $ sudo snap install slcli - - (or to get the latest release) - - $ sudo snap install slcli --edge - - + + [softlayer] + username = imsUsername + verify = False + endpoint_url = https://internal.app0lb.dal10.softlayer.local/v3/internal/xmlrpc/ -The most up-to-date version of this library can be found on the SoftLayer -GitHub public repositories at http://github.com/softlayer. For questions regarding the use of this library please post to Stack Overflow at https://stackoverflow.com/ and your posts with “SoftLayer” so our team can easily find your post. To report a bug with this library please create an Issue on github. - -InsecurePlatformWarning Notice ------------------------------- -This library relies on the `requests `_ library to make HTTP requests. On Python versions below Python 2.7.9, requests has started emitting a security warning (InsecurePlatformWarning) due to insecurities with creating SSL connections. To resolve this, upgrade to Python 2.7.9+ or follow the instructions here: http://stackoverflow.com/a/29099439. Basic Usage ----------- -- `The Complete Command Directory `_ +.. code-block:: bash + + $ islcli login + $ islcli -a vs list + Advanced Usage -------------- @@ -90,19 +58,18 @@ You can automatically set some parameters via environment variables with by usin $ export SLCLI_VERBOSE=3 $ export SLCLI_FORMAT=json - $ slcli vs list + $ slcli -a vs list is equivalent to .. code-block:: bash - $ slcli -vvv --format=json vs list + $ slcli -vvv --format=json -a vs list Getting Help ------------ -Bugs and feature requests about this library should have a `GitHub issue `_ opened about them. -Issues with the Softlayer API itself should be addressed by opening a ticket. +Feel free to open an issue if you think there is a bug, or a feature you want. Or asking in #sl-api on IBM slack. This is considered an unofficial project however, so questions might take some time to get answered. Examples @@ -110,6 +77,21 @@ Examples A curated list of examples on how to use this library can be found at `SLDN `_ + +.. code-block:: python + + import SoftLayer + client = SoftLayer.employee_client() + username = input("Username:") + password = input("Password:") + yubikey = input("Yubi key:") + client.authenticate_with_password(username, password, yubikey) + result = client.call('SoftLayer_Account', 'getObject', id="12345", mask="mask[id]") + + +After logging in with `authenticate_with_password` the EmployeeClient will try to automatically refresh the login token when it gets a TokenExpired exception. It will also record the token in the config file for future use in the CLI. + + Debugging --------- To get the exact API call that this library makes, you can do the following. @@ -131,7 +113,7 @@ If you are using the library directly in python, you can do something like this. class invoices(): def __init__(self): - self.client = SoftLayer.Client() + self.client = SoftLayer.EmployeeClient() debugger = SoftLayer.DebugTransport(self.client.transport) self.client.transport = debugger @@ -153,16 +135,13 @@ If you are using the library directly in python, you can do something like this. System Requirements ------------------- -* Python 3.5, 3.6, 3.7, 3.8, or 3.9. -* A valid SoftLayer API username and key. -* A connection to SoftLayer's private network is required to use - our private network API endpoints. +* Python 3.7, 3.8, or 3.9. +* A valid SoftLayer Employee API username, password, Yubi Key +* A connection to SoftLayer's Employee VPN Python 2.7 Support ------------------ -As of version 5.8.0 SoftLayer-Python will no longer support python2.7, which is `End Of Life as of 2020 `_ . -If you cannot install python 3.6+ for some reason, you will need to use a version of softlayer-python <= 5.7.2 - +Python 2.7 is `End Of Life as of 2020 `_ . Its not supported, you will need to upgrade to python 3.7 at least. Python Packages @@ -173,6 +152,7 @@ Python Packages * prompt_toolkit >= 2 * pygments >= 2.0.0 * urllib3 >= 1.24 +* Rich Copyright --------- diff --git a/SoftLayer/CLI/login.py b/SoftLayer/CLI/login.py index 13afe1e8a..9b9fbe887 100644 --- a/SoftLayer/CLI/login.py +++ b/SoftLayer/CLI/login.py @@ -33,7 +33,6 @@ def cli(env): username = settings.get('username') or os.environ.get('SLCLI_USER', None) password = os.environ.get('SLCLI_PASSWORD', '') yubi = None -# client = EmployeeClient(config_file=env.config_file) client = env.client # Might already be logged in, try and refresh token diff --git a/setup.py b/setup.py index fc233a095..683daa914 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ # pylint: disable=inconsistent-return-statements -DESCRIPTION = "A library for SoftLayer's API" +DESCRIPTION = "A library for SoftLayer's IMS API" if os.path.exists('README.rst'): with codecs.open('README.rst', 'r', 'utf-8') as readme_file: @@ -15,7 +15,7 @@ LONG_DESCRIPTION = DESCRIPTION setup( - name='SoftLayer', + name='SoftLayer-Internal', version='6.0.2', description=DESCRIPTION, long_description=LONG_DESCRIPTION, @@ -25,14 +25,13 @@ packages=find_packages(exclude=['tests']), license='MIT', zip_safe=False, - url='http://github.com/softlayer/softlayer-python', + url='https://github.ibm.com/SoftLayer/internal-softlayer-cli', entry_points={ 'console_scripts': [ - 'slcli = SoftLayer.CLI.core:main', - 'sl = SoftLayer.CLI.deprecated:main', + 'islcli = SoftLayer.CLI.core:main', ], }, - python_requires='>=3.6', + python_requires='>=3.7', install_requires=[ 'prettytable >= 2.5.0', 'click >= 8.0.4', @@ -42,7 +41,7 @@ 'urllib3 >= 1.24', 'rich == 12.3.0' ], - keywords=['softlayer', 'cloud', 'slcli'], + keywords=['islcli'], classifiers=[ 'Environment :: Console', 'Environment :: Web Environment', @@ -51,8 +50,6 @@ 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Topic :: Software Development :: Libraries :: Python Modules', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', From b8c548b40fa570661ce5676688028c0b5a132901 Mon Sep 17 00:00:00 2001 From: CHRISTOPHER GALLO Date: Tue, 7 Jun 2022 11:27:55 -0500 Subject: [PATCH 011/710] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index af53ab3e9..6def08c28 100644 --- a/README.rst +++ b/README.rst @@ -18,7 +18,7 @@ Install via pip: .. code-block:: bash - $ git clone https://github.ibm.com/SoftLayer/internal-softlayer-cli + $ git clone https://github.ibm.com/SoftLayer/internal-softlayer-cli $ cd internal-softlayer-cli $ python setup.py install $ ./islcli login From d6343257e31a2344d6e16307ced211be253ac77b Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 14 Jun 2022 17:04:24 -0400 Subject: [PATCH 012/710] add a new command on block object-storage details --- SoftLayer/CLI/block/object_storage_detail.py | 34 +++++++++++++++++++ SoftLayer/CLI/routes.py | 1 + .../fixtures/SoftLayer_Network_Storage.py | 6 +++- SoftLayer/managers/block.py | 10 ++++++ tests/CLI/modules/block_tests.py | 8 ++++- tests/managers/block_tests.py | 4 +++ 6 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 SoftLayer/CLI/block/object_storage_detail.py diff --git a/SoftLayer/CLI/block/object_storage_detail.py b/SoftLayer/CLI/block/object_storage_detail.py new file mode 100644 index 000000000..e7aa69135 --- /dev/null +++ b/SoftLayer/CLI/block/object_storage_detail.py @@ -0,0 +1,34 @@ +"""Display details for a specified cloud object storage.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +@click.command(cls=SoftLayer.CLI.command.SLCommand, ) +@click.argument('object_id') +@environment.pass_env +def cli(env, object_id): + """Display details for a cloud object storage.""" + + block_manager = SoftLayer.BlockStorageManager(env.client) + + cloud = block_manager.get_volume_details(object_id) + bucket = block_manager.get_buckets(object_id) + + table = formatting.KeyValueTable(['Name', 'Value']) + table.align['Name'] = 'r' + table.align['Value'] = 'l' + + table.add_row(['Id', cloud.get('id')]) + table.add_row(['Username', cloud.get('username')]) + table.add_row(['Name Service Resource', cloud['serviceResource']['name']]) + table.add_row(['Type Service Resource', cloud['serviceResource']['type']['type']]) + table.add_row(['Datacenter', cloud['serviceResource']['datacenter']['name']]) + table.add_row(['Storage type', cloud['storageType']['keyName']]) + table.add_row(['Bytes Used', formatting.b_to_gb(bucket[0]['bytesUsed'])]) + table.add_row(['Bucket name', bucket[0]['name']]) + + env.fout(table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index a9f64d845..2c3072020 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -127,6 +127,7 @@ ('block:volume-convert', 'SoftLayer.CLI.block.convert:cli'), ('block:volume-set-note', 'SoftLayer.CLI.block.set_note:cli'), ('block:object-list', 'SoftLayer.CLI.block.object_list:cli'), + ('block:object-storage-detail', 'SoftLayer.CLI.block.object_storage_detail:cli'), ('email', 'SoftLayer.CLI.email'), ('email:list', 'SoftLayer.CLI.email.list:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage.py b/SoftLayer/fixtures/SoftLayer_Network_Storage.py index acf369d16..409f1fd89 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Storage.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage.py @@ -149,7 +149,11 @@ } ], 'serviceProviderId': 1, - 'serviceResource': {'datacenter': {'id': 449500, 'name': 'dal05'}}, + 'serviceResource': {'datacenter': {'id': 449500, 'name': 'dal05'}, + 'name': 'Cleversafe - US Region', + 'type': { + 'type': 'CLEVERSAFE_SVC_API' + }}, 'serviceResourceBackendIpAddress': '10.1.2.3', 'serviceResourceName': 'Storage Type 01 Aggregate staaspar0101_pc01', 'snapshotCapacityGb': '10', diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 260941454..1910107d5 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -207,3 +207,13 @@ def get_cloud_list(self): mask = 'mask[id,username,billingItem,storageType, notes]' return self.client.call('Account', 'getHubNetworkStorage', mask=mask) + + def get_buckets(self, object_id): + """Return buckets data of the cloud storage. + + :param object_id cloud object storage identifier + + Returns: Get buckets + + """ + return self.client.call('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'getBuckets', id=object_id) diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index f909683a0..96f471f19 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -444,7 +444,7 @@ def test_snapshot_restore(self): self.assert_no_fail(result) self.assertEqual(result.output, 'Block volume 12345678 is being' - ' restored using snapshot 87654321\n') + ' restored using snapshot 87654321\n') @mock.patch('SoftLayer.BlockStorageManager.order_snapshot_space') def test_snapshot_order_order_not_placed(self, order_mock): @@ -825,3 +825,9 @@ def test_object_list(self): self.assert_no_fail(result) self.assert_called_with('SoftLayer_Account', 'getHubNetworkStorage') + + def test_object_details(self): + result = self.run_command(['block', 'object-storage-detail', '1234']) + + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'getBuckets') diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index 854813310..b0e808146 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -1119,3 +1119,7 @@ def test_convert_block_depdupe(self): def test_get_cloud_list(self): self.block.get_cloud_list() self.assert_called_with('SoftLayer_Account', 'getHubNetworkStorage') + + def test_get_bucket(self): + self.block.get_buckets(1234) + self.assert_called_with('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'getBuckets') From 754fa4585619fa57be3dab2fcaec36212dcaec30 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 14 Jun 2022 17:10:21 -0400 Subject: [PATCH 013/710] add documentation --- docs/cli/block.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/cli/block.rst b/docs/cli/block.rst index 799070b57..585087752 100644 --- a/docs/cli/block.rst +++ b/docs/cli/block.rst @@ -160,6 +160,10 @@ Block Commands :prog: block object-list :show-nested: +.. click:: SoftLayer.CLI.block.object_storage_detail:cli + :prog: block object-storage-detail + :show-nested: + .. click:: SoftLayer.CLI.block.duplicate_convert_status:cli :prog: block duplicate-convert-status :show-nested: From 98435fe084eb538c06b0e15c05c08013e9717339 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 16 Jun 2022 17:03:14 -0400 Subject: [PATCH 014/710] new feature block object-storage permissions command --- .../CLI/block/object_storage_permission.py | 44 +++++++++++++++++++ SoftLayer/CLI/routes.py | 1 + ..._Network_Storage_Hub_Cleversafe_Account.py | 41 +++++++++++++++++ SoftLayer/managers/storage.py | 20 +++++++++ docs/cli/block.rst | 4 ++ tests/CLI/modules/block_tests.py | 9 +++- tests/managers/block_tests.py | 8 ++++ 7 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 SoftLayer/CLI/block/object_storage_permission.py diff --git a/SoftLayer/CLI/block/object_storage_permission.py b/SoftLayer/CLI/block/object_storage_permission.py new file mode 100644 index 000000000..49b5769ce --- /dev/null +++ b/SoftLayer/CLI/block/object_storage_permission.py @@ -0,0 +1,44 @@ +"""Display details for a specified cloud object storage.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +@click.command(cls=SoftLayer.CLI.command.SLCommand, ) +@click.argument('object_id') +@environment.pass_env +def cli(env, object_id): + """Display details for a cloud object storage.""" + + block_manager = SoftLayer.BlockStorageManager(env.client) + + cloud = block_manager.get_network_message_delivery_accounts(object_id) + end_points = block_manager.get_end_points(object_id) + + table = formatting.Table(['name', 'value']) + + table_credentials = formatting.Table(['Id', 'Username', 'Password', 'Description']) + + for credential in cloud.get('credentials'): + table_credentials.add_row([credential.get('id'), + credential.get('username'), + credential.get('password'), + credential['type']['description']]) + + table_url = formatting.Table(['region', + 'location', + 'type', + 'url']) + for end_point in end_points: + table_url.add_row([end_point.get('region') or '', + end_point.get('location') or '', + end_point.get('type'), + end_point.get('url'), ]) + + table.add_row(['UUID', cloud.get('uuid')]) + table.add_row(['Credemtial', table_credentials]) + table.add_row(['EndPoint URL', table_url]) + env.fout(table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index a9f64d845..038937680 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -127,6 +127,7 @@ ('block:volume-convert', 'SoftLayer.CLI.block.convert:cli'), ('block:volume-set-note', 'SoftLayer.CLI.block.set_note:cli'), ('block:object-list', 'SoftLayer.CLI.block.object_list:cli'), + ('block:object-storage-permission', 'SoftLayer.CLI.block.object_storage_permission:cli'), ('email', 'SoftLayer.CLI.email'), ('email:list', 'SoftLayer.CLI.email.list:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage_Hub_Cleversafe_Account.py b/SoftLayer/fixtures/SoftLayer_Network_Storage_Hub_Cleversafe_Account.py index ab72576e4..75b71e015 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Storage_Hub_Cleversafe_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage_Hub_Cleversafe_Account.py @@ -47,6 +47,47 @@ } ] +getEndpoints = [ + { + 'legacy': False, + 'region': 'us-geo', + 'type': 'public', + 'url': 's3.us.cloud-object-storage.appdomain.cloud' + }, + { + 'legacy': False, + 'region': 'us-geo', + 'type': 'private', + 'url': 's3.private.us.cloud-object-storage.appdomain.cloud' + } +] getCredentialLimit = 2 credentialDelete = True + +getObject = { + 'id': 123456, + 'username': 'SLOSC307608-1', + 'credentials': [ + { + 'id': 1933496, + 'password': 'Um1Bp420FIFNvAg2QHjn5Sci2c2x4RNDXpVDDvnf', + 'username': 'Kv9aNIhtNa7ZRceCTgep', + 'type': { + 'description': 'A credential for generating S3 Compatible Signatures.', + 'keyName': 'S3_COMPATIBLE_SIGNATURE' + } + }, + { + + 'id': 1732820, + 'password': 'q6NtwqeuXDaRqGc0Jrugg2sDgbatyNsoN9sPEmjo', + 'username': '252r9BN8ibuDSQAXLOeL', + 'type': { + 'description': 'A credential for generating S3 Compatible Signatures.', + 'keyName': 'S3_COMPATIBLE_SIGNATURE', + } + } + ], + 'uuid': '01c449c484ae4a58a42d9b79d4c5e4ed' +} diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index 7403f2fcd..fd6ff70e0 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -9,6 +9,7 @@ from SoftLayer.managers import storage_utils from SoftLayer import utils + # pylint: disable=too-many-public-methods @@ -586,3 +587,22 @@ def convert_dupe_status(self, volume_id): return self.client.call('Network_Storage', 'getDuplicateConversionStatus', id=volume_id) + + def get_network_message_delivery_accounts(self, object_id): + """Return object data of the cloud storage. + + :param object_id cloud object storage identifier + Returns: Get instances + """ + object_mask = 'mask[uuid,credentials]' + return self.client.call('SoftLayer_Network_Storage_Hub_Cleversafe_Account', + 'getObject', mask=object_mask, id=object_id) + + def get_end_points(self, object_id): + """Returns a collection of endpoint URLs available to this IBM Cloud Object Storage account. + + :param object_id cloud object storage identifier + Returns: Returns a collection of endpoint URLs. + """ + return self.client.call('SoftLayer_Network_Storage_Hub_Cleversafe_Account', + 'getEndpoints', id=object_id) diff --git a/docs/cli/block.rst b/docs/cli/block.rst index 799070b57..72a41c2db 100644 --- a/docs/cli/block.rst +++ b/docs/cli/block.rst @@ -164,3 +164,7 @@ Block Commands :prog: block duplicate-convert-status :show-nested: +.. click:: SoftLayer.CLI.block.object_storage_permission:cli + :prog: block object-storage-permission + :show-nested: + diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index f909683a0..d3c144496 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -444,7 +444,7 @@ def test_snapshot_restore(self): self.assert_no_fail(result) self.assertEqual(result.output, 'Block volume 12345678 is being' - ' restored using snapshot 87654321\n') + ' restored using snapshot 87654321\n') @mock.patch('SoftLayer.BlockStorageManager.order_snapshot_space') def test_snapshot_order_order_not_placed(self, order_mock): @@ -825,3 +825,10 @@ def test_object_list(self): self.assert_no_fail(result) self.assert_called_with('SoftLayer_Account', 'getHubNetworkStorage') + + def test_object_permissions(self): + result = self.run_command(['block', 'object-storage-permission', '1234']) + + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'getObject') + self.assert_called_with('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'getEndpoints') diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index 854813310..f50387fa7 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -1119,3 +1119,11 @@ def test_convert_block_depdupe(self): def test_get_cloud_list(self): self.block.get_cloud_list() self.assert_called_with('SoftLayer_Account', 'getHubNetworkStorage') + + def test_get_cloud_endPoints(self): + self.block.get_end_points(123456) + self.assert_called_with('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'getEndpoints') + + def test_get_cloud_object(self): + self.block.get_network_message_delivery_accounts(123456) + self.assert_called_with('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'getObject') From 38d8a78886e981c444a3f36b8a09e49ec24f9cd5 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 17 Jun 2022 18:33:04 -0400 Subject: [PATCH 015/710] fix the team code review comments --- .../CLI/block/object_storage_permission.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/SoftLayer/CLI/block/object_storage_permission.py b/SoftLayer/CLI/block/object_storage_permission.py index 49b5769ce..85d5aae5d 100644 --- a/SoftLayer/CLI/block/object_storage_permission.py +++ b/SoftLayer/CLI/block/object_storage_permission.py @@ -1,4 +1,4 @@ -"""Display details for a specified cloud object storage.""" +"""Display permission details for a cloud object storage.""" # :license: MIT, see LICENSE for more details. import click @@ -11,16 +11,16 @@ @click.argument('object_id') @environment.pass_env def cli(env, object_id): - """Display details for a cloud object storage.""" + """Display permission details for a cloud object storage.""" block_manager = SoftLayer.BlockStorageManager(env.client) cloud = block_manager.get_network_message_delivery_accounts(object_id) end_points = block_manager.get_end_points(object_id) - table = formatting.Table(['name', 'value']) + table = formatting.Table(['Name', 'Value']) - table_credentials = formatting.Table(['Id', 'Username', 'Password', 'Description']) + table_credentials = formatting.Table(['Id', 'Access Key ID', 'Secret Access Key', 'Description']) for credential in cloud.get('credentials'): table_credentials.add_row([credential.get('id'), @@ -28,10 +28,10 @@ def cli(env, object_id): credential.get('password'), credential['type']['description']]) - table_url = formatting.Table(['region', - 'location', - 'type', - 'url']) + table_url = formatting.Table(['Region', + 'Location', + 'Type', + 'URL']) for end_point in end_points: table_url.add_row([end_point.get('region') or '', end_point.get('location') or '', @@ -39,6 +39,6 @@ def cli(env, object_id): end_point.get('url'), ]) table.add_row(['UUID', cloud.get('uuid')]) - table.add_row(['Credemtial', table_credentials]) - table.add_row(['EndPoint URL', table_url]) + table.add_row(['Credentials', table_credentials]) + table.add_row(['EndPoint URL´s', table_url]) env.fout(table) From 802ab46fe87e7fdc6f1f32484f0475e55d97d504 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 20 Jun 2022 11:24:50 -0400 Subject: [PATCH 016/710] slcli account bandwidth-pools-detail command displays an error with bandwidth pools that has devices --- SoftLayer/CLI/account/bandwidth_pools_detail.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/account/bandwidth_pools_detail.py b/SoftLayer/CLI/account/bandwidth_pools_detail.py index e6420a5b9..8a523bcff 100644 --- a/SoftLayer/CLI/account/bandwidth_pools_detail.py +++ b/SoftLayer/CLI/account/bandwidth_pools_detail.py @@ -37,17 +37,17 @@ def cli(env, identifier): inbound = '-' table.add_row(['Inbound Usage', inbound]) if bandwidths['hardware'] != []: - table.add_row(['hardware', _bw_table(bandwidths['hardware'])]) + table.add_row(['hardware', *(_bw_table(bandwidths['hardware']))]) else: table.add_row(['hardware', 'Not Found']) if bandwidths['virtualGuests'] != []: - table.add_row(['virtualGuests', _virtual_table(bandwidths['virtualGuests'])]) + table.add_row(['virtualGuests', *(_virtual_table(bandwidths['virtualGuests']))]) else: table.add_row(['virtualGuests', 'Not Found']) if bandwidths['bareMetalInstances'] != []: - table.add_row(['Netscaler', _bw_table(bandwidths['bareMetalInstances'])]) + table.add_row(['Netscaler', *(_bw_table(bandwidths['bareMetalInstances']))]) else: table.add_row(['Netscaler', 'Not Found']) From 827709010da926ba29051331da3cc6071e8f383b Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 21 Jun 2022 12:51:39 -0400 Subject: [PATCH 017/710] fix the vlan table --- SoftLayer/CLI/vlan/detail.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/vlan/detail.py b/SoftLayer/CLI/vlan/detail.py index a89d7a3b6..a353c2a50 100644 --- a/SoftLayer/CLI/vlan/detail.py +++ b/SoftLayer/CLI/vlan/detail.py @@ -51,7 +51,7 @@ def cli(env, identifier, no_vs, no_hardware): subnet_table.add_row(['usable ips', subnet.get('usableIpAddressCount')]) subnets.append(subnet_table) - table.add_row(['subnets', subnets]) + table.add_row(['subnets', *subnets]) server_columns = ['hostname', 'domain', 'public_ip', 'private_ip'] @@ -63,7 +63,7 @@ def cli(env, identifier, no_vs, no_hardware): vsi.get('domain'), vsi.get('primaryIpAddress'), vsi.get('primaryBackendIpAddress')]) - table.add_row(['vs', vs_table]) + table.add_row(['vs', *vs_table]) else: table.add_row(['vs', 'none']) @@ -75,7 +75,7 @@ def cli(env, identifier, no_vs, no_hardware): hardware.get('domain'), hardware.get('primaryIpAddress'), hardware.get('primaryBackendIpAddress')]) - table.add_row(['hardware', hw_table]) + table.add_row(['hardware', *hw_table]) else: table.add_row(['hardware', 'none']) From 1e26c614a09fa1dff6cb3cd3fadc471f894a9398 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 23 Jun 2022 15:23:01 -0400 Subject: [PATCH 018/710] fix the unit test --- SoftLayer/CLI/vlan/detail.py | 32 ++++++++++---------- SoftLayer/fixtures/SoftLayer_Network_Vlan.py | 30 ++++++++++++++++++ 2 files changed, 46 insertions(+), 16 deletions(-) diff --git a/SoftLayer/CLI/vlan/detail.py b/SoftLayer/CLI/vlan/detail.py index a353c2a50..b8e0bd2f9 100644 --- a/SoftLayer/CLI/vlan/detail.py +++ b/SoftLayer/CLI/vlan/detail.py @@ -38,20 +38,20 @@ def cli(env, identifier, no_vs, no_hardware): table.add_row(['primary_router', utils.lookup(vlan, 'primaryRouter', 'fullyQualifiedDomainName')]) table.add_row(['Gateway/Firewall', get_gateway_firewall(vlan)]) - subnets = [] - for subnet in vlan.get('subnets', []): - subnet_table = formatting.KeyValueTable(['name', 'value']) - subnet_table.align['name'] = 'r' - subnet_table.align['value'] = 'l' - subnet_table.add_row(['id', subnet.get('id')]) - subnet_table.add_row(['identifier', subnet.get('networkIdentifier')]) - subnet_table.add_row(['netmask', subnet.get('netmask')]) - subnet_table.add_row(['gateway', subnet.get('gateway', formatting.blank())]) - subnet_table.add_row(['type', subnet.get('subnetType')]) - subnet_table.add_row(['usable ips', subnet.get('usableIpAddressCount')]) - subnets.append(subnet_table) - - table.add_row(['subnets', *subnets]) + + if vlan.get('subnets'): + subnet_table = formatting.Table(['id', 'identifier', 'netmask', 'gateway', 'type', 'usable ips']) + for subnet in vlan.get('subnets'): + subnet_table.add_row([subnet.get('id'), + subnet.get('networkIdentifier'), + subnet.get('netmask'), + subnet.get('gateway') or formatting.blank(), + subnet.get('subnetType'), + subnet.get('usableIpAddressCount')]) + # subnets.append(subnet_table) + table.add_row(['subnets', subnet_table]) + else: + table.add_row(['subnets', 'none']) server_columns = ['hostname', 'domain', 'public_ip', 'private_ip'] @@ -63,7 +63,7 @@ def cli(env, identifier, no_vs, no_hardware): vsi.get('domain'), vsi.get('primaryIpAddress'), vsi.get('primaryBackendIpAddress')]) - table.add_row(['vs', *vs_table]) + table.add_row(['vs', vs_table]) else: table.add_row(['vs', 'none']) @@ -75,7 +75,7 @@ def cli(env, identifier, no_vs, no_hardware): hardware.get('domain'), hardware.get('primaryIpAddress'), hardware.get('primaryBackendIpAddress')]) - table.add_row(['hardware', *hw_table]) + table.add_row(['hardware', hw_table]) else: table.add_row(['hardware', 'none']) diff --git a/SoftLayer/fixtures/SoftLayer_Network_Vlan.py b/SoftLayer/fixtures/SoftLayer_Network_Vlan.py index 83d501bd0..61e70a56d 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Vlan.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Vlan.py @@ -14,6 +14,36 @@ 'notes': 'test cli', 'orderItemId': 147258, }, + 'subnets': [ + { + 'broadcastAddress': '169.46.22.127', + 'cidr': 28, + 'gateway': '169.46.22.113', + 'id': 1804813, + 'netmask': '255.255.255.240', + 'networkIdentifier': '169.46.22.112', + 'networkVlanId': 1404267, + 'subnetType': 'ADDITIONAL_PRIMARY', + 'totalIpAddresses': '16', + 'usableIpAddressCount': '13', + 'addressSpace': 'PUBLIC' + }, + { + 'broadcastAddress': '150.239.7.191', + 'cidr': 27, + 'gateway': '150.239.7.161', + 'id': 2415982, + 'netmask': '255.255.255.224', + 'networkIdentifier': '150.239.7.160', + 'networkVlanId': 1404267, + 'subnetType': 'SECONDARY_ON_VLAN', + 'totalIpAddresses': '32', + 'usableIpAddressCount': '29', + 'version': 4, + 'addressSpace': 'PUBLIC' + }], + 'hardware': [], + 'virtualGuests': [], 'tagReferences': [], } From 11458c734daf6d734f62e74c00e74bca05718f94 Mon Sep 17 00:00:00 2001 From: Robert Houtenbrink Date: Tue, 28 Jun 2022 11:24:57 -0500 Subject: [PATCH 019/710] Update README.rst --- README.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 6def08c28..c946c3572 100644 --- a/README.rst +++ b/README.rst @@ -32,12 +32,12 @@ The config file is located at `~/.softlayer` or `~/AppData/Roaming/softlayer` fo Your config file should look something like this for using the islcli. Beware the `islcli` and `slcli` use the same config for the moment. You need to set `verify = False` in the config because the internal endpoint uses a self-signed SSL certificate. .. code-block:: bash - [softlayer] - username = imsUsername + timeout = 600 verify = False - endpoint_url = https://internal.app0lb.dal10.softlayer.local/v3/internal/xmlrpc/ - + username = imsUsername + endpoint_url = http://internal.applb.dal10.softlayer.local/v3.1/internal/xmlrpc/ + userid = imsUserId Basic Usage From 5f8c0aa80845ef28b05d6450b7566bbd10fe77f8 Mon Sep 17 00:00:00 2001 From: Robert Houtenbrink Date: Tue, 28 Jun 2022 11:25:29 -0500 Subject: [PATCH 020/710] Update login.py --- SoftLayer/CLI/login.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/login.py b/SoftLayer/CLI/login.py index 9b9fbe887..8b0efa61e 100644 --- a/SoftLayer/CLI/login.py +++ b/SoftLayer/CLI/login.py @@ -56,7 +56,7 @@ def cli(env): if username is None: username = input("Username: ") click.echo("Username: {}".format(username)) - if password is None: + if not password: password = env.getpass("Password: ") click.echo("Password: {}".format(censor_password(password))) yubi = input("Yubi: ") From 6a66a03706041cbc3fe04a1eff1a26973fcb4759 Mon Sep 17 00:00:00 2001 From: Robert Houtenbrink Date: Tue, 28 Jun 2022 12:01:14 -0500 Subject: [PATCH 021/710] Update README.rst --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index c946c3572..e98cb5b30 100644 --- a/README.rst +++ b/README.rst @@ -32,14 +32,14 @@ The config file is located at `~/.softlayer` or `~/AppData/Roaming/softlayer` fo Your config file should look something like this for using the islcli. Beware the `islcli` and `slcli` use the same config for the moment. You need to set `verify = False` in the config because the internal endpoint uses a self-signed SSL certificate. .. code-block:: bash - [softlayer] + + [softlayer] timeout = 600 verify = False username = imsUsername endpoint_url = http://internal.applb.dal10.softlayer.local/v3.1/internal/xmlrpc/ userid = imsUserId - Basic Usage ----------- From 04ed706961301014805489414efd3dc8e3fba983 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 30 Jun 2022 13:49:16 -0500 Subject: [PATCH 022/710] v6.1.0 Changelog and version bump --- CHANGELOG.md | 55 +++++++++++++++++++++++++++++++++++++++++++++ SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1749efaea..fbc7da7d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,61 @@ # Change Log + +## [6.1.0] - 2022-06-30 + +## Major Updates +* [Rich](https://github.com/Textualize/rich) tables by @allmightyspiff in https://github.com/softlayer/softlayer-python/pull/1646 +* [Rich](https://github.com/Textualize/rich) Text support by @allmightyspiff in https://github.com/softlayer/softlayer-python/pull/1635 + +Rich Text and Rich Tables will modernize the output of the SLCLI to be a little nicer to look at, with colors and other highlighting. + +![image](https://user-images.githubusercontent.com/7408017/176753783-f6a4a43a-53ac-4600-a24f-21362f152747.png) +![image](https://user-images.githubusercontent.com/7408017/176753845-32af33f0-454f-4bab-ac63-1ae3db788ede.png) + + +## What's Changed +* slcli licenses is missing the help text by @caberos in https://github.com/softlayer/softlayer-python/pull/1605 +* Add a warning if user orders in a POD that is being closed by @caberos in https://github.com/softlayer/softlayer-python/pull/1600 +* updated number of updates in the command account event-detail by @edsonarios in https://github.com/softlayer/softlayer-python/pull/1609 +* Add an orderBy filter to slcli vlan list by @caberos in https://github.com/softlayer/softlayer-python/pull/1599 +* Add options to print a specific table in command slcli account events by @edsonarios in https://github.com/softlayer/softlayer-python/pull/1611 +* Update global ip assign/unassign to use new API by @caberos in https://github.com/softlayer/softlayer-python/pull/1614 +* Ability to route/unroute subnets by @caberos in https://github.com/softlayer/softlayer-python/pull/1615 +* Improved successful response to command - slcli account cancel-item by @edsonarios in https://github.com/softlayer/softlayer-python/pull/1617 +* Improved successful response to command - slcli virtual edit by @edsonarios in https://github.com/softlayer/softlayer-python/pull/1618 +* Improved successful response to command - slcli vlan cancel by @edsonarios in https://github.com/softlayer/softlayer-python/pull/1619 +* Mishandling of domain and hostname data in `slcli account item-detail` by @edsonarios in https://github.com/softlayer/softlayer-python/pull/1621 +* Unable to get VSI details when last TXN is "Software install is finis… by @caberos in https://github.com/softlayer/softlayer-python/pull/1625 +* new command on autoscale delete by @caberos in https://github.com/softlayer/softlayer-python/pull/1628 +* Incorrect table title is displayed when an Auto Scale Group is scaled to reduce members by @BrianSantivanez in https://github.com/softlayer/softlayer-python/pull/1629 +* slcli autoscale create by @caberos in https://github.com/softlayer/softlayer-python/pull/1623 +* Soap transport by @allmightyspiff in https://github.com/softlayer/softlayer-python/pull/1626 +* fix issue on loadbal order command by @caberos in https://github.com/softlayer/softlayer-python/pull/1633 +* Policy is not added when an AutoScale Group is created by @caberos in https://github.com/softlayer/softlayer-python/pull/1637 +* When `slcli event-log` not return any event log the command display an error by @BrianSantivanez in https://github.com/softlayer/softlayer-python/pull/1641 +* add new columns on vlan list(premium, tags) by @caberos in https://github.com/softlayer/softlayer-python/pull/1645 +* fixed documentation build issues by @allmightyspiff in https://github.com/softlayer/softlayer-python/pull/1648 +* Improved successful response to command - slcli licenses cancel by @edsonarios in https://github.com/softlayer/softlayer-python/pull/1653 +* update the firewall list by @caberos in https://github.com/softlayer/softlayer-python/pull/1649 +* Updated readme by @allmightyspiff in https://github.com/softlayer/softlayer-python/pull/1656 +* Update `slcli firewall detail` to handle multi vlan firewalls by @BrianSantivanez in https://github.com/softlayer/softlayer-python/pull/1651 +* New command for getting duplicate convert status by @ko101 in https://github.com/softlayer/softlayer-python/pull/1655 +* Fixed TOX errors by @allmightyspiff in https://github.com/softlayer/softlayer-python/pull/1661 +* add a new feature to get all cloud object storage by @caberos in https://github.com/softlayer/softlayer-python/pull/1662 +* Update `slcli report bandwidth` command by @BrianSantivanez in https://github.com/softlayer/softlayer-python/pull/1664 +* add firewall monitoring command by @caberos in https://github.com/softlayer/softlayer-python/pull/1657 +* add a new command on block object-storage details by @caberos in https://github.com/softlayer/softlayer-python/pull/1666 +* slcli account bandwidth-pools-detail command displays an error with b… by @caberos in https://github.com/softlayer/softlayer-python/pull/1670 +* new feature block object-storage permissions command by @caberos in https://github.com/softlayer/softlayer-python/pull/1668 +* fix the vlan table by @caberos in https://github.com/softlayer/softlayer-python/pull/1672 + +## New Contributors +* @BrianSantivanez made their first contribution in https://github.com/softlayer/softlayer-python/pull/1629 + +**Full Changelog**: https://github.com/softlayer/softlayer-python/compare/v6.0.2...v6.1.0 + + ## [6.0.2] - 2022-03-30 ## What's Changed diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 32ebd4ec4..1fe4278a1 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v6.0.2' +VERSION = 'v6.1.0' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index fc233a095..4fc80e4a9 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name='SoftLayer', - version='6.0.2', + version='6.1.0', description=DESCRIPTION, long_description=LONG_DESCRIPTION, long_description_content_type='text/x-rst', From 60c78b24d7d7e695fb21a699749fe27132268288 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 1 Jul 2022 09:49:49 -0500 Subject: [PATCH 023/710] #1678 fixed an issue with the item-list table --- SoftLayer/CLI/order/item_list.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/SoftLayer/CLI/order/item_list.py b/SoftLayer/CLI/order/item_list.py index eef805d21..32dd1f090 100644 --- a/SoftLayer/CLI/order/item_list.py +++ b/SoftLayer/CLI/order/item_list.py @@ -8,18 +8,19 @@ from SoftLayer.managers import ordering from SoftLayer.utils import lookup -COLUMNS = ['category', 'keyName', 'description', 'priceId'] -COLUMNS_ITEM_PRICES = ['keyName', 'priceId', 'Hourly', 'Monthly', 'Restriction'] -COLUMNS_ITEM_PRICES_LOCATION = ['keyName', 'priceId', 'Hourly', 'Monthly', 'Restriction'] +COLUMNS = ['Category', 'KeyName', 'Description', 'Price Id'] +COLUMNS_ITEM_PRICES = ['KeyName', 'Price Id', 'Hourly', 'Monthly', 'Restriction'] +COLUMNS_ITEM_PRICES_LOCATION = ['KeyName', 'Price Id', 'Hourly', 'Monthly', 'Restriction'] @click.command(cls=SLCommand) @click.argument('package_keyname') @click.option('--keyword', '-k', help="A word (or string) used to filter item names.") @click.option('--category', '-c', help="Category code to filter items by") -@click.option('--prices', '-p', is_flag=True, help='Use --prices to list the server item prices, and to list the ' - 'Item Prices by location, add it to the --prices option using ' - 'location KeyName, e.g. --prices AMSTERDAM02') +@click.option('--prices', '-p', is_flag=True, + help='Use --prices to list the server item prices, and to list the ' + 'Item Prices by location, add it to the --prices option using ' + 'location KeyName, e.g. --prices AMSTERDAM02') @click.argument('location', required=False) @environment.pass_env def cli(env, package_keyname, keyword, category, prices, location=None): @@ -66,7 +67,7 @@ def cli(env, package_keyname, keyword, category, prices, location=None): for item in sorted_items[category_name]: table_items_detail.add_row([category_name, item['keyName'], item['description'], get_price(item)]) tables.append(table_items_detail) - env.fout(formatting.listing(tables, separator='\n')) + env.fout(tables) def sort_items(items): From aa4de89b909550f667f5e1f642fc0f91c00998cf Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 1 Jul 2022 10:07:21 -0500 Subject: [PATCH 024/710] Fixed unit tests --- SoftLayer/CLI/order/item_list.py | 2 +- tests/CLI/modules/order_tests.py | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/SoftLayer/CLI/order/item_list.py b/SoftLayer/CLI/order/item_list.py index 32dd1f090..173e30a2f 100644 --- a/SoftLayer/CLI/order/item_list.py +++ b/SoftLayer/CLI/order/item_list.py @@ -17,7 +17,7 @@ @click.argument('package_keyname') @click.option('--keyword', '-k', help="A word (or string) used to filter item names.") @click.option('--category', '-c', help="Category code to filter items by") -@click.option('--prices', '-p', is_flag=True, +@click.option('--prices', '-p', is_flag=True, help='Use --prices to list the server item prices, and to list the ' 'Item Prices by location, add it to the --prices option using ' 'location KeyName, e.g. --prices AMSTERDAM02') diff --git a/tests/CLI/modules/order_tests.py b/tests/CLI/modules/order_tests.py index d493b67f9..c15415946 100644 --- a/tests/CLI/modules/order_tests.py +++ b/tests/CLI/modules/order_tests.py @@ -50,9 +50,9 @@ def test_item_list_prices(self): self.assert_no_fail(result) output = json.loads(result.output) - self.assertEqual(output[0][0]['priceId'], 1007) + self.assertEqual(output[0][0]['Price Id'], 1007) self.assertEqual(output[0][1]['Restriction'], '- - - -') - self.assertEqual(output[0][1]['keyName'], 'KeyName015') + self.assertEqual(output[0][1]['KeyName'], 'KeyName015') self.assert_called_with('SoftLayer_Product_Package', 'getItems') def test_item_list_location_keyname(self): @@ -61,8 +61,8 @@ def test_item_list_location_keyname(self): self.assert_no_fail(result) output = json.loads(result.output) self.assertEqual(output[0][0]['Hourly'], 0.0) - self.assertEqual(output[0][1]['keyName'], 'KeyName015') - self.assertEqual(output[0][1]['priceId'], 1144) + self.assertEqual(output[0][1]['KeyName'], 'KeyName015') + self.assertEqual(output[0][1]['Price Id'], 1144) self.assert_called_with('SoftLayer_Product_Package', 'getItemPrices') def test_item_list_location_name(self): @@ -71,8 +71,8 @@ def test_item_list_location_name(self): self.assert_no_fail(result) output = json.loads(result.output) self.assertEqual(output[0][0]['Hourly'], 0.0) - self.assertEqual(output[0][1]['keyName'], 'KeyName015') - self.assertEqual(output[0][1]['priceId'], 1144) + self.assertEqual(output[0][1]['KeyName'], 'KeyName015') + self.assertEqual(output[0][1]['Price Id'], 1144) self.assert_called_with('SoftLayer_Product_Package', 'getItemPrices') def test_item_list_category_keyword(self): @@ -81,8 +81,8 @@ def test_item_list_category_keyword(self): self.assert_no_fail(result) output = json.loads(result.output) self.assertEqual(output[0][0]['Hourly'], 0.0) - self.assertEqual(output[0][1]['keyName'], 'KeyName015') - self.assertEqual(output[0][1]['priceId'], 1144) + self.assertEqual(output[0][1]['KeyName'], 'KeyName015') + self.assertEqual(output[0][1]['Price Id'], 1144) self.assert_called_with('SoftLayer_Product_Package', 'getItemPrices') def test_package_list(self): From fe56157abf8f72a398393835bd29c9684741fe24 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 1 Jul 2022 11:13:11 -0500 Subject: [PATCH 025/710] #1675 #1581 updateing release job to actually publish to pypi instead of the test instance --- .github/workflows/release.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b009483c3..e322a5b4a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,6 +46,7 @@ jobs: - name: Publish 📦 to Test PyPI uses: pypa/gh-action-pypi-publish@master with: - password: ${{ secrets.CGALLO_TEST_PYPI }} - repository_url: https://test.pypi.org/legacy/ + user: __token__ + password: ${{ secrets.CGALLO_PYPI }} + repository_url: https://pypi.org/legacy/ From 18bcb87b614787a23961009a99c3dee8494dd692 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 4 Jul 2022 15:22:03 -0400 Subject: [PATCH 026/710] add the block volume-options command --- SoftLayer/CLI/block/options.py | 61 ++++++++++++++++++++++++ SoftLayer/CLI/routes.py | 1 + SoftLayer/fixtures/SoftLayer_Location.py | 2 +- docs/cli/block.rst | 4 ++ tests/CLI/modules/block_tests.py | 5 ++ 5 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 SoftLayer/CLI/block/options.py diff --git a/SoftLayer/CLI/block/options.py b/SoftLayer/CLI/block/options.py new file mode 100644 index 000000000..89ea72a27 --- /dev/null +++ b/SoftLayer/CLI/block/options.py @@ -0,0 +1,61 @@ +"""List all options for ordering a block storage.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI.command import SLCommand +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + +volume_size = ['20', '40', '80', '100', '250', '500', '1000', '2000-3000', '4000-7000', '8000-9000', '10000-12000'] + + +@click.command(cls=SLCommand) +@environment.pass_env +def cli(env): + """List all options for ordering a block storage""" + + network_manager = SoftLayer.NetworkManager(env.client) + datacenters = network_manager.get_datacenter() + + table = formatting.Table(['Name', 'Value'], title='Volume table') + + table.add_row(['Storage Type', 'performance,endurance']) + table.add_row(['Size (GB)', str(volume_size)]) + table.add_row(['OS Type', 'HYPER_V,LINUX,VMWARE,WINDOWS_2008,WINDOWS_GPT,WINDOWS,XEN']) + iops_table = formatting.Table(['Size (GB)', '20', '40', '80', '100', '250', '500', '1000', '2000-3000', + '4000-7000', '8000-9000', '10000-12000'], title='IOPS table') + snapshot_table = formatting.Table(['Storage Size (GB)', 'Available Snapshot Size (GB)'], title="Snapshot table") + + datacenter_str = ','.join([str(dc['longName']) for dc in datacenters]) + iops_table.add_row(['Size (GB)', *volume_size]) + iops_table.add_row(['Min IOPS', '100', '100', '100', '100', '100', '100', '100', '200', '300', '500', '1000']) + iops_table.add_row(['Max IOPS', '1000', '2000', '4000', '6000', '6000', '6000 or 10000', '6000 or 20000', + '6000 or 40000', '6000 or 48000', '6000 or 48000', '6000 or 48000']) + # table.add_row(['iops', iops_table]) + table.add_row(['Tier', '0.25,2,4,10']) + table.add_row(['location', datacenter_str]) + + snapshot_table.add_row(['20', '0,5,10,20']) + snapshot_table.add_row(['40', '0,5,10,20,40']) + snapshot_table.add_row(['80', '0,5,10,20,40,60,80']) + snapshot_table.add_row(['100', '0,5,10,20,40,60,80,100']) + snapshot_table.add_row(['250', '0,5,10,20,40,60,80,100,150,200,250']) + snapshot_table.add_row(['500', '0,5,10,20,40,60,80,100,150,200,250,300,350,400,450,500']) + snapshot_table.add_row(['1000', '0,5,10,20,40,60,80,100,150,200,250,300,350,400,450,500,600,700,1000']) + snapshot_table.add_row(['2000-3000', '0,5,10,20,40,60,80,100,150,200,250,300,350,400,450,500,600,700,1000,2000']) + snapshot_table.add_row( + ['4000-7000', '0,5,10,20,40,60,80,100,150,200,250,300,350,400,450,500,600,700,1000,2000,4000']) + snapshot_table.add_row( + ['8000-9000', '0,5,10,20,40,60,80,100,150,200,250,300,350,400,450,500,600,700,1000,2000,4000']) + snapshot_table.add_row( + ['10000-12000', '0,5,10,20,40,60,80,100,150,200,250,300,350,400,450,500,600,700,1000,2000,4000']) + + # table.add_row(['Snapshot Size (GB)', snapshot_table]) + table.add_row(['Note:', + 'IOPs limit above 6000 available in select data centers, refer to:' + 'http://knowledgelayer.softlayer.com/articles/new-ibm-block-and-file-storage-location-and-features']) + env.fout(table) + env.fout(iops_table) + env.fout(snapshot_table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index a9f64d845..c3e07264e 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -125,6 +125,7 @@ ('block:volume-limits', 'SoftLayer.CLI.block.limit:cli'), ('block:volume-refresh', 'SoftLayer.CLI.block.refresh:cli'), ('block:volume-convert', 'SoftLayer.CLI.block.convert:cli'), + ('block:volume-options', 'SoftLayer.CLI.block.options:cli'), ('block:volume-set-note', 'SoftLayer.CLI.block.set_note:cli'), ('block:object-list', 'SoftLayer.CLI.block.object_list:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Location.py b/SoftLayer/fixtures/SoftLayer_Location.py index d770d4d82..8875b345a 100644 --- a/SoftLayer/fixtures/SoftLayer_Location.py +++ b/SoftLayer/fixtures/SoftLayer_Location.py @@ -9,4 +9,4 @@ "longName": "San Jose 1", "name": "sjc01" }] -getDatacenters = [{'id': 1854895, 'name': 'dal13', 'regions': [{'keyname': 'DALLAS13'}]}] +getDatacenters = [{'id': 1854895, 'longName': 'dallas 13', 'name': 'dal13', 'regions': [{'keyname': 'DALLAS13'}]}] diff --git a/docs/cli/block.rst b/docs/cli/block.rst index 799070b57..dc682576a 100644 --- a/docs/cli/block.rst +++ b/docs/cli/block.rst @@ -83,6 +83,10 @@ Block Commands :prog: block volume-count :show-nested: +.. click:: SoftLayer.CLI.block.options:cli + :prog: block volume-options + :show-nested: + .. click:: SoftLayer.CLI.block.detail:cli :prog: block volume-detail :show-nested: diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index f909683a0..3148b8880 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -792,6 +792,11 @@ def test_dep_dupe_convert(self): self.assert_no_fail(result) + def test_volume_options(self): + result = self.run_command(['block', 'volume-options']) + + self.assert_no_fail(result) + @mock.patch('SoftLayer.BlockStorageManager.volume_set_note') def test_volume_set_note(self, set_note): set_note.return_value = True From 83a4f2946673da0e0ee9ba79aed88cddf731a23c Mon Sep 17 00:00:00 2001 From: edsonarios Date: Wed, 6 Jul 2022 11:13:43 -0400 Subject: [PATCH 027/710] updated command +-------------------------------+ | datacenter | public | private | |------------+--------+---------| +-------------------------------+ and updated unit test --- .../CLI/object_storage/list_endpoints.py | 75 ++++++++++++++++--- SoftLayer/managers/object_storage.py | 26 +------ tests/CLI/modules/object_storage_tests.py | 65 ++++++++++++---- tests/managers/object_storage_tests.py | 56 +++++++++----- 4 files changed, 159 insertions(+), 63 deletions(-) diff --git a/SoftLayer/CLI/object_storage/list_endpoints.py b/SoftLayer/CLI/object_storage/list_endpoints.py index f78fd46f3..06dda65a9 100644 --- a/SoftLayer/CLI/object_storage/list_endpoints.py +++ b/SoftLayer/CLI/object_storage/list_endpoints.py @@ -2,26 +2,83 @@ # :license: MIT, see LICENSE for more details. import click - import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer import utils @click.command(cls=SoftLayer.CLI.command.SLCommand, ) +@click.argument('identifier') @environment.pass_env -def cli(env): +def cli(env, identifier): """List object storage endpoints.""" mgr = SoftLayer.ObjectStorageManager(env.client) - endpoints = mgr.list_endpoints() + endpoints = mgr.list_endpoints(identifier) + + final_end_points = [] - table = formatting.Table(['datacenter', 'public', 'private']) + table = formatting.Table(['Legacy', 'EndPoint Type', 'Public/Private', 'Location/Region', 'Url']) for endpoint in endpoints: - table.add_row([ - endpoint['datacenter']['name'], - endpoint['public'], - endpoint['private'], - ]) + data = [endpoint['legacy'], end_point_return(endpoint['region']), public_private(endpoint['type']), + location_region(endpoint), endpoint['url']] + final_end_points.append(data) + + final_end_points = sort_endpoint(final_end_points) + table = add_array_to_table(table, final_end_points) env.fout(table) + + +def add_array_to_table(table, array_datas): + """Add an array to a table""" + for array in array_datas: + table.add_row([array[0], array[1], array[2], array[3], array[4]]) + return table + + +def end_point_return(endpoint): + """Returns end point type""" + if endpoint == 'singleSite': + return 'Single Site' + if endpoint == 'regional': + return 'Region' + return 'Cross Region' + + +def public_private(data): + """Returns public or private in capital letter""" + if data == 'public': + return 'Public' + return 'Private' + + +def location_region(endpoint): + """Returns location if it exists otherwise region""" + if utils.lookup(endpoint, 'location'): + return endpoint['location'] + return endpoint['region'] + + +def sort_endpoint(endpoints): + """Sort the all endpoints for public or private""" + endpoint_type = '' + if len(endpoints) > 0: + endpoint_type = endpoints[0][1] + public = [] + private = [] + array_final = [] + for endpoint in endpoints: + if endpoint[1] != endpoint_type: + endpoint_type = endpoint[1] + array_final = array_final + public + private + public.clear() + private.clear() + if endpoint[2] == 'Public': + public.append(endpoint) + else: + private.append(endpoint) + + array_final = array_final + public + private + return array_final diff --git a/SoftLayer/managers/object_storage.py b/SoftLayer/managers/object_storage.py index 5efadeabc..a0ed5012e 100644 --- a/SoftLayer/managers/object_storage.py +++ b/SoftLayer/managers/object_storage.py @@ -13,10 +13,6 @@ id,username,notes,vendorName,serviceResource ]''' -ENDPOINT_MASK = '''mask(SoftLayer_Network_Storage_Hub_Swift)[ - id,storageNodes[id,backendIpAddress,frontendIpAddress,datacenter] -]''' - class ObjectStorageManager(utils.IdentifierMixin, object): """Manager for SoftLayer Object Storage accounts. @@ -40,26 +36,10 @@ def list_accounts(self, object_mask=None, object_filter=None, limit=10): filter=object_filter, limit=limit) - def list_endpoints(self): + def list_endpoints(self, identifier): """Lists the known object storage endpoints.""" - _filter = { - 'hubNetworkStorage': {'vendorName': {'operation': 'Swift'}}, - } - endpoints = [] - network_storage = self.client.call('Account', - 'getHubNetworkStorage', - mask=ENDPOINT_MASK, - limit=1, - filter=_filter) - if network_storage: - for node in network_storage['storageNodes']: - endpoints.append({ - 'datacenter': node['datacenter'], - 'public': node['frontendIpAddress'], - 'private': node['backendIpAddress'], - }) - - return endpoints + + return self.client.call('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'getEndpoints', id=identifier) def create_credential(self, identifier): """Create object storage credential. diff --git a/tests/CLI/modules/object_storage_tests.py b/tests/CLI/modules/object_storage_tests.py index e76fddff1..38612a1cf 100644 --- a/tests/CLI/modules/object_storage_tests.py +++ b/tests/CLI/modules/object_storage_tests.py @@ -22,22 +22,61 @@ def test_list_accounts(self): ) def test_list_endpoints(self): - accounts = self.set_mock('SoftLayer_Account', 'getHubNetworkStorage') - accounts.return_value = { - 'storageNodes': [{ - 'datacenter': {'name': 'dal05'}, - 'frontendIpAddress': 'https://dal05/auth/v1.0/', - 'backendIpAddress': 'https://dal05/auth/v1.0/'} - ], - } - - result = self.run_command(['object-storage', 'endpoints']) + accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'getEndpoints') + accounts.return_value = [ + { + 'legacy': False, + 'region': 'us-geo', + 'type': 'public', + 'url': 's3.us.cloud-object-storage.appdomain.cloud' + }, + { + 'legacy': False, + 'region': 'us-geo', + 'type': 'private', + 'url': 's3.private.us.cloud-object-storage.appdomain.cloud' + }, + { + 'legacy': True, + 'location': 'dal06', + 'region': 'regional', + 'type': 'public', + 'url': 's3.dal.cloud-object-storage.appdomain.cloud' + }, + { + 'legacy': True, + 'location': 'ams03', + 'region': 'singleSite', + 'type': 'private', + 'url': 's3.ams.cloud-object-storage.appdomain.cloud' + }, + ] + + result = self.run_command(['object-storage', 'endpoints', '123']) self.assert_no_fail(result) self.assertEqual(json.loads(result.output), - [{'datacenter': 'dal05', - 'private': 'https://dal05/auth/v1.0/', - 'public': 'https://dal05/auth/v1.0/'}]) + [{'Legacy': False, + 'EndPoint Type': 'Cross Region', + 'Public/Private': 'Public', + 'Location/Region': 'us-geo', + 'Url': 's3.us.cloud-object-storage.appdomain.cloud'}, + {'Legacy': False, + 'EndPoint Type': 'Cross Region', + 'Public/Private': 'Private', + 'Location/Region': 'us-geo', + 'Url': 's3.private.us.cloud-object-storage.appdomain.cloud'}, + {'Legacy': True, + 'EndPoint Type': 'Region', + 'Public/Private': 'Public', + 'Location/Region': 'dal06', + 'Url': 's3.dal.cloud-object-storage.appdomain.cloud'}, + {'Legacy': True, + 'EndPoint Type': 'Single Site', + 'Public/Private': 'Private', + 'Location/Region': 'ams03', + 'Url': 's3.ams.cloud-object-storage.appdomain.cloud'} + ]) def test_create_credential(self): result = self.run_command(['object-storage', 'credential', 'create', '100']) diff --git a/tests/managers/object_storage_tests.py b/tests/managers/object_storage_tests.py index 16081cc83..5d2b0783c 100644 --- a/tests/managers/object_storage_tests.py +++ b/tests/managers/object_storage_tests.py @@ -21,28 +21,48 @@ def test_list_accounts(self): fixtures.SoftLayer_Account.getHubNetworkStorage) def test_list_endpoints(self): - accounts = self.set_mock('SoftLayer_Account', 'getHubNetworkStorage') - accounts.return_value = { - 'storageNodes': [{ - 'datacenter': {'name': 'dal05'}, - 'frontendIpAddress': 'https://dal05/auth/v1.0/', - 'backendIpAddress': 'https://dal05/auth/v1.0/'} - ], - } - endpoints = self.object_storage.list_endpoints() + accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'getEndpoints') + accounts.return_value = [ + { + 'legacy': False, + 'region': 'us-geo', + 'type': 'public', + 'url': 's3.us.cloud-object-storage.appdomain.cloud' + }, + { + 'legacy': False, + 'region': 'us-geo', + 'type': 'private', + 'url': 's3.private.us.cloud-object-storage.appdomain.cloud' + } + ] + + endpoints = self.object_storage.list_endpoints(123) self.assertEqual(endpoints, - [{'datacenter': {'name': 'dal05'}, - 'private': 'https://dal05/auth/v1.0/', - 'public': 'https://dal05/auth/v1.0/'}]) + [ + { + 'legacy': False, + 'region': 'us-geo', + 'type': 'public', + 'url': 's3.us.cloud-object-storage.appdomain.cloud' + }, + { + 'legacy': False, + 'region': 'us-geo', + 'type': 'private', + 'url': 's3.private.us.cloud-object-storage.appdomain.cloud' + } + ]) def test_list_endpoints_no_results(self): - accounts = self.set_mock('SoftLayer_Account', 'getHubNetworkStorage') - accounts.return_value = { - 'storageNodes': [], - } - endpoints = self.object_storage.list_endpoints() + accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'getEndpoints') + accounts.return_value = [ + {} + ] + + endpoints = self.object_storage.list_endpoints(123) self.assertEqual(endpoints, - []) + [{}]) def test_create_credential(self): accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'credentialCreate') From 989bd3d4843dc08f5d53c87a362a271adde604fe Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 6 Jul 2022 11:30:10 -0400 Subject: [PATCH 028/710] add the file volume-options command --- SoftLayer/CLI/file/options.py | 61 ++++++++++++++++++++++++ SoftLayer/CLI/routes.py | 1 + SoftLayer/fixtures/SoftLayer_Location.py | 2 +- docs/cli/file.rst | 4 ++ tests/CLI/modules/file_tests.py | 4 ++ 5 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 SoftLayer/CLI/file/options.py diff --git a/SoftLayer/CLI/file/options.py b/SoftLayer/CLI/file/options.py new file mode 100644 index 000000000..68f5f05fe --- /dev/null +++ b/SoftLayer/CLI/file/options.py @@ -0,0 +1,61 @@ +"""List all options for ordering a file storage.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI.command import SLCommand +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + +volume_size = ['20', '40', '80', '100', '250', '500', '1000', '2000-3000', '4000-7000', '8000-9000', '10000-12000'] + + +@click.command(cls=SLCommand) +@environment.pass_env +def cli(env): + """List all options for ordering a file storage""" + + network_manager = SoftLayer.NetworkManager(env.client) + datacenters = network_manager.get_datacenter() + + table = formatting.Table(['Name', 'Value'], title='Volume table') + + table.add_row(['Storage Type', 'performance,endurance']) + table.add_row(['Size (GB)', str(volume_size)]) + + iops_table = formatting.Table(['Size (GB)', '20', '40', '80', '100', '250', '500', '1000', '2000-3000', + '4000-7000', '8000-9000', '10000-12000'], title='IOPS table') + snapshot_table = formatting.Table(['Storage Size (GB)', 'Available Snapshot Size (GB)'], title="Snapshot table") + + datacenter_str = ','.join([str(dc['longName']) for dc in datacenters]) + iops_table.add_row(['Size (GB)', *volume_size]) + iops_table.add_row(['Min IOPS', '100', '100', '100', '100', '100', '100', '100', '200', '300', '500', '1000']) + iops_table.add_row(['Max IOPS', '1000', '2000', '4000', '6000', '6000', '6000 or 10000', '6000 or 20000', + '6000 or 40000', '6000 or 48000', '6000 or 48000', '6000 or 48000']) + # table.add_row(['iops', iops_table]) + table.add_row(['Tier', '0.25,2,4,10']) + table.add_row(['location', datacenter_str]) + + snapshot_table.add_row(['20', '0,5,10,20']) + snapshot_table.add_row(['40', '0,5,10,20,40']) + snapshot_table.add_row(['80', '0,5,10,20,40,60,80']) + snapshot_table.add_row(['100', '0,5,10,20,40,60,80,100']) + snapshot_table.add_row(['250', '0,5,10,20,40,60,80,100,150,200,250']) + snapshot_table.add_row(['500', '0,5,10,20,40,60,80,100,150,200,250,300,350,400,450,500']) + snapshot_table.add_row(['1000', '0,5,10,20,40,60,80,100,150,200,250,300,350,400,450,500,600,700,1000']) + snapshot_table.add_row(['2000-3000', '0,5,10,20,40,60,80,100,150,200,250,300,350,400,450,500,600,700,1000,2000']) + snapshot_table.add_row( + ['4000-7000', '0,5,10,20,40,60,80,100,150,200,250,300,350,400,450,500,600,700,1000,2000,4000']) + snapshot_table.add_row( + ['8000-9000', '0,5,10,20,40,60,80,100,150,200,250,300,350,400,450,500,600,700,1000,2000,4000']) + snapshot_table.add_row( + ['10000-12000', '0,5,10,20,40,60,80,100,150,200,250,300,350,400,450,500,600,700,1000,2000,4000']) + + # table.add_row(['Snapshot Size (GB)', snapshot_table]) + table.add_row(['Note:', + 'IOPs limit above 6000 available in select data centers, refer to:' + 'http://knowledgelayer.softlayer.com/articles/new-ibm-block-and-file-storage-location-and-features']) + env.fout(table) + env.fout(iops_table) + env.fout(snapshot_table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 75e42102e..e370ec7a2 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -174,6 +174,7 @@ ('file:volume-limits', 'SoftLayer.CLI.file.limit:cli'), ('file:volume-refresh', 'SoftLayer.CLI.file.refresh:cli'), ('file:volume-convert', 'SoftLayer.CLI.file.convert:cli'), + ('file:volume-options', 'SoftLayer.CLI.file.options:cli'), ('file:volume-set-note', 'SoftLayer.CLI.file.set_note:cli'), ('firewall', 'SoftLayer.CLI.firewall'), diff --git a/SoftLayer/fixtures/SoftLayer_Location.py b/SoftLayer/fixtures/SoftLayer_Location.py index d770d4d82..6ad47b387 100644 --- a/SoftLayer/fixtures/SoftLayer_Location.py +++ b/SoftLayer/fixtures/SoftLayer_Location.py @@ -9,4 +9,4 @@ "longName": "San Jose 1", "name": "sjc01" }] -getDatacenters = [{'id': 1854895, 'name': 'dal13', 'regions': [{'keyname': 'DALLAS13'}]}] +getDatacenters = [{'id': 1854895, "longName": "Dallas 13", 'name': 'dal13', 'regions': [{'keyname': 'DALLAS13'}]}] diff --git a/docs/cli/file.rst b/docs/cli/file.rst index eca4f5b33..55f1b085b 100644 --- a/docs/cli/file.rst +++ b/docs/cli/file.rst @@ -123,6 +123,10 @@ File Commands :prog: file volume-set-note :show-nested: +.. click:: SoftLayer.CLI.file.options:cli + :prog: file volume-options + :show-nested: + .. click:: SoftLayer.CLI.file.replication.disaster_recovery_failover:cli :prog: file disaster-recovery-failover :show-nested: diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index 6bd56bd07..6943399b7 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -798,3 +798,7 @@ def test_snapshot_get_notification_status(self, status): result = self.run_command(['file', 'snapshot-get-notification-status', '999']) self.assert_no_fail(result) self.assertIn(expect, result.output) + + def test_volume_options(self): + result = self.run_command(['file', 'volume-options']) + self.assert_no_fail(result) From 9e9d70cfd0669ff2f8e72db2ef382135dd62d8ba Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 7 Jul 2022 16:02:17 -0500 Subject: [PATCH 029/710] #1687 fixed issues where a message warned users about closing datacenters --- SoftLayer/CLI/hardware/create.py | 6 ++---- SoftLayer/CLI/order/place.py | 26 ++++++++++--------------- SoftLayer/CLI/virt/create.py | 4 +--- SoftLayer/managers/network.py | 15 ++++++++++++++ tests/CLI/modules/order_tests.py | 12 ++++++++---- tests/CLI/modules/server_tests.py | 23 ++++++++++++++++++++++ tests/CLI/modules/vs/vs_create_tests.py | 12 ++++++++++++ tests/managers/network_tests.py | 16 +++++++++++++++ 8 files changed, 87 insertions(+), 27 deletions(-) diff --git a/SoftLayer/CLI/hardware/create.py b/SoftLayer/CLI/hardware/create.py index 52ebfa5c5..682c07bef 100644 --- a/SoftLayer/CLI/hardware/create.py +++ b/SoftLayer/CLI/hardware/create.py @@ -44,7 +44,6 @@ def cli(env, **args): network = SoftLayer.NetworkManager(env.client) pods = network.get_closed_pods() - closure = [] # Get the SSH keys ssh_keys = [] @@ -104,9 +103,8 @@ def cli(env, **args): if do_create: for pod in pods: - if args.get('datacenter') in str(pod['name']): - closure.append(pod['name']) - click.secho(click.style('Warning: Closed soon: %s' % (', '.join(closure)), fg='yellow')) + if args.get('datacenter') in pod['name']: + click.secho('Warning: Closed soon: {}'.format(pod['name']), fg='yellow') if not (env.skip_confirmations or formatting.confirm( "This action will incur charges on your account. Continue?")): raise exceptions.CLIAbort('Aborting dedicated server order.') diff --git a/SoftLayer/CLI/order/place.py b/SoftLayer/CLI/order/place.py index eeb9ad2ef..351b96780 100644 --- a/SoftLayer/CLI/order/place.py +++ b/SoftLayer/CLI/order/place.py @@ -12,9 +12,8 @@ from SoftLayer.managers import NetworkManager from SoftLayer.managers import ordering -COLUMNS = ['keyName', - 'description', - 'cost', ] + +COLUMNS = ['keyName', 'description', 'cost'] @click.command(cls=SLCommand) @@ -22,17 +21,11 @@ @click.argument('location') @click.option('--preset', help="The order preset (if required by the package)") -@click.option('--verify', - is_flag=True, +@click.option('--verify', is_flag=True, help="Flag denoting whether or not to only verify the order, not place it") -@click.option('--quantity', - type=int, - default=1, +@click.option('--quantity', type=int, default=1, help="The quantity of the item being ordered") -@click.option('--billing', - type=click.Choice(['hourly', 'monthly']), - default='hourly', - show_default=True, +@click.option('--billing', type=click.Choice(['hourly', 'monthly']), default='hourly', show_default=True, help="Billing rate") @click.option('--complex-type', help=("The complex type of the order. Starts with 'SoftLayer_Container_Product_Order'.")) @@ -68,8 +61,12 @@ def cli(env, package_keyname, location, preset, verify, billing, complex_type, manager = ordering.OrderingManager(env.client) network = NetworkManager(env.client) + # Check if this location is going to be shutdown soon. pods = network.get_closed_pods() - closure = [] + location_dc = network.get_datacenter_by_keyname(location) + for pod in pods: + if location_dc.get('name') in pod.get('name'): + click.secho('Warning: Closed soon: {}'.format(pod.get('name')), fg='yellow') if extras: try: @@ -96,9 +93,6 @@ def cli(env, package_keyname, location, preset, verify, billing, complex_type, ]) else: - for pod in pods: - closure.append(pod['name']) - click.secho(click.style('Warning: Closed soon: %s' % (', '.join(closure)), fg='yellow')) if not (env.skip_confirmations or formatting.confirm( "This action will incur charges on your account. Continue?")): raise exceptions.CLIAbort("Aborting order.") diff --git a/SoftLayer/CLI/virt/create.py b/SoftLayer/CLI/virt/create.py index 8f6a8f643..3a581a5bd 100644 --- a/SoftLayer/CLI/virt/create.py +++ b/SoftLayer/CLI/virt/create.py @@ -221,13 +221,11 @@ def cli(env, **args): network = SoftLayer.NetworkManager(env.client) pods = network.get_closed_pods() - closure = [] if do_create: for pod in pods: if args.get('datacenter') in str(pod['name']): - closure.append(pod['name']) - click.secho(click.style('Warning: Closed soon: %s' % (', '.join(closure)), fg='yellow')) + click.secho('Warning: Closed soon: {}'.format(pod['name']), fg='yellow') if not (env.skip_confirmations or formatting.confirm( "This action will incur charges on your account. Continue?")): raise exceptions.CLIAbort('Aborting virtual server order.') diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 2480974a9..ae4cc8298 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -851,3 +851,18 @@ def get_datacenter(self, _filter=None, datacenter=None): _filter = {"name": {"operation": datacenter}} return self.client.call('SoftLayer_Location', 'getDatacenters', filter=_filter, limit=1) + + def get_datacenter_by_keyname(self, keyname=None): + """Calls SoftLayer_Location::getDatacenters() + + returns datacenter list. + """ + mask = "mask[id,longName,name,regions[keyname,description]]" + _filter = {} + if keyname: + _filter = {"regions": {"keyname": {"operation": keyname}}} + + result = self.client.call('SoftLayer_Location', 'getDatacenters', filter=_filter, limit=1, mask=mask) + if len(result) >= 1: + return result[0] + return {} diff --git a/tests/CLI/modules/order_tests.py b/tests/CLI/modules/order_tests.py index c15415946..246aa920a 100644 --- a/tests/CLI/modules/order_tests.py +++ b/tests/CLI/modules/order_tests.py @@ -7,6 +7,8 @@ import sys import tempfile +from unittest import mock as mock + from SoftLayer.CLI import exceptions from SoftLayer import testing @@ -141,10 +143,12 @@ def test_place(self): self.assert_no_fail(result) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') - self.assertIn('Warning: Closed soon: ams01.pod01, wdc07.pod01, TEST00.pod2', result.output) + self.assertNotIn('Warning: Closed soon: dal13', result.output) self.assertIn('"status": "APPROVED"', result.output) - def test_place_with_quantity(self): + @mock.patch('SoftLayer.managers.network.NetworkManager.get_datacenter_by_keyname') + def test_place_with_quantity(self, keyname_mock): + keyname_mock.return_value = {"name": "ams01"} order_date = '2017-04-04 07:39:20' order = {'orderId': 1234, 'orderDate': order_date, 'placedOrder': {'status': 'APPROVED'}} verify_mock = self.set_mock('SoftLayer_Product_Order', 'verifyOrder') @@ -155,12 +159,12 @@ def test_place_with_quantity(self): place_mock.return_value = order items_mock.return_value = self._get_order_items() - result = self.run_command(['-y', 'order', 'place', '--quantity=2', 'package', 'DALLAS13', 'ITEM1', + result = self.run_command(['-y', 'order', 'place', '--quantity=2', 'package', 'AMSTERDAM', 'ITEM1', '--complex-type', 'SoftLayer_Container_Product_Order_Thing']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') - self.assertIn('Warning: Closed soon: ams01.pod01, wdc07.pod01, TEST00.pod2', result.output) + self.assertIn('Warning: Closed soon: ams01.pod01', result.output) self.assertIn('"status": "APPROVED"', result.output) def test_place_extras_parameter_fail(self): diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 9f8653c01..cc30c70ed 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -401,6 +401,17 @@ def test_create_server(self, order_mock): self.assertIn('Warning: Closed soon: TEST00.pod2', result.output) self.assertIn('"id": 98765', result.output) + result = self.run_command(['--really', 'server', 'create', + '--size=S1270_8GB_2X1TBSATA_NORAID', + '--hostname=test', + '--domain=example.com', + '--datacenter=mex01', + '--os=OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', + ]) + + self.assert_no_fail(result) + self.assertNotIn('Warning: Closed soon: mex01', result.output) + @mock.patch('SoftLayer.CLI.template.export_to_template') def test_create_server_with_export(self, export_mock): @@ -1015,3 +1026,15 @@ def test_sensor_discrete(self): def test_monitoring(self): result = self.run_command(['hardware', 'monitoring', '100']) self.assert_no_fail(result) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_check_for_closing(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['vs', 'create', '--hostname', 'TEST', '--domain', 'TESTING', + '--flavor', 'B1_2X8X25', '--datacenter', 'ams01', '--os', 'UBUNTU_LATEST']) + self.assert_no_fail(result) + self.assertIn('Warning: Closed soon: ams01', result.output) + result = self.run_command(['vs', 'create', '--hostname', 'TEST', '--domain', 'TESTING', + '--flavor', 'B1_2X8X25', '--datacenter', 'mex01', '--os', 'UBUNTU_LATEST']) + self.assert_no_fail(result) + self.assertNotIn('Warning: Closed soon: mex01', result.output) diff --git a/tests/CLI/modules/vs/vs_create_tests.py b/tests/CLI/modules/vs/vs_create_tests.py index efcb8cbc3..468bc5339 100644 --- a/tests/CLI/modules/vs/vs_create_tests.py +++ b/tests/CLI/modules/vs/vs_create_tests.py @@ -722,3 +722,15 @@ def test_create_with_userdata(self, confirm_mock): api_call = self.calls('SoftLayer_Product_Order', 'placeOrder') # Doing this because the placeOrder args are huge and mostly not needed to test self.assertEqual(api_call[0].args[0]['virtualGuests'], expected_guest) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_check_for_closing(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['vs', 'create', '--hostname', 'TEST', '--domain', 'TESTING', + '--flavor', 'B1_2X8X25', '--datacenter', 'ams01', '--os', 'UBUNTU_LATEST']) + self.assert_no_fail(result) + self.assertIn('Warning: Closed soon: ams01', result.output) + result = self.run_command(['vs', 'create', '--hostname', 'TEST', '--domain', 'TESTING', + '--flavor', 'B1_2X8X25', '--datacenter', 'mex01', '--os', 'UBUNTU_LATEST']) + self.assert_no_fail(result) + self.assertNotIn('Warning: Closed soon: mex01', result.output) diff --git a/tests/managers/network_tests.py b/tests/managers/network_tests.py index 81a91faf7..04c2d8d6e 100644 --- a/tests/managers/network_tests.py +++ b/tests/managers/network_tests.py @@ -637,3 +637,19 @@ def test_route(self): def test_get_all_datacenter(self): self.network.get_datacenter() self.assert_called_with('SoftLayer_Location', 'getDatacenters') + self.network.get_datacenter(datacenter="dal11") + expected_filter = {"name": {"operation": "dal11"}} + self.assert_called_with('SoftLayer_Location', 'getDatacenters', filter=expected_filter) + + def test_get_datacenter_by_keyname(self): + # normal operation + result = self.network.get_datacenter_by_keyname("TEST01") + expected_filter = {"regions": {"keyname": {"operation": "TEST01"}}} + self.assert_called_with('SoftLayer_Location', 'getDatacenters', filter=expected_filter) + self.assertEqual(result.get('name'), 'dal13') + + # an "empty" result + SoftLayer_Location = self.set_mock('SoftLayer_Location', 'getDatacenters') + SoftLayer_Location.return_value = [] + result = self.network.get_datacenter_by_keyname("TEST01") + self.assertEqual(result, {}) From 09e00c074a0b88a3b499425efb0b460766f3d2b2 Mon Sep 17 00:00:00 2001 From: edsonarios Date: Fri, 8 Jul 2022 10:58:05 -0400 Subject: [PATCH 030/710] Solved comments --- .../CLI/object_storage/list_endpoints.py | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/object_storage/list_endpoints.py b/SoftLayer/CLI/object_storage/list_endpoints.py index 06dda65a9..a437e4735 100644 --- a/SoftLayer/CLI/object_storage/list_endpoints.py +++ b/SoftLayer/CLI/object_storage/list_endpoints.py @@ -19,10 +19,17 @@ def cli(env, identifier): final_end_points = [] - table = formatting.Table(['Legacy', 'EndPoint Type', 'Public/Private', 'Location/Region', 'Url']) + table = formatting.Table(['Location/Region', 'Url', 'EndPoint Type', 'Public/Private', 'Legacy']) + table.align['Location/Region'] = 'l' + table.align['Url'] = 'l' for endpoint in endpoints: - data = [endpoint['legacy'], end_point_return(endpoint['region']), public_private(endpoint['type']), - location_region(endpoint), endpoint['url']] + data = { + 'Location/Region': location_region(endpoint), + 'Url': endpoint['url'], + 'EndPoint Type': end_point_return(endpoint['region']), + 'Public/Private': public_private(endpoint['type']), + 'Legacy': endpoint['legacy'] + } final_end_points.append(data) final_end_points = sort_endpoint(final_end_points) @@ -34,7 +41,11 @@ def cli(env, identifier): def add_array_to_table(table, array_datas): """Add an array to a table""" for array in array_datas: - table.add_row([array[0], array[1], array[2], array[3], array[4]]) + table.add_row([array['Location/Region'], + array['Url'], + array['EndPoint Type'], + array['Public/Private'], + array['Legacy']]) return table @@ -63,19 +74,20 @@ def location_region(endpoint): def sort_endpoint(endpoints): """Sort the all endpoints for public or private""" + first_data = 0 endpoint_type = '' if len(endpoints) > 0: - endpoint_type = endpoints[0][1] + endpoint_type = endpoints[first_data]['EndPoint Type'] public = [] private = [] array_final = [] for endpoint in endpoints: - if endpoint[1] != endpoint_type: - endpoint_type = endpoint[1] + if endpoint['EndPoint Type'] != endpoint_type: + endpoint_type = endpoint['EndPoint Type'] array_final = array_final + public + private public.clear() private.clear() - if endpoint[2] == 'Public': + if endpoint['Public/Private'] == 'Public': public.append(endpoint) else: private.append(endpoint) From dbf076e7d6a79094dc6850d8cb6dff1854502503 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 11 Jul 2022 10:55:24 -0400 Subject: [PATCH 031/710] fix the team code review comments --- SoftLayer/CLI/block/options.py | 70 +++++++++++++------------------- SoftLayer/managers/ordering.py | 19 ++++++++- tests/managers/ordering_tests.py | 8 ++++ 3 files changed, 54 insertions(+), 43 deletions(-) diff --git a/SoftLayer/CLI/block/options.py b/SoftLayer/CLI/block/options.py index 89ea72a27..7c3a11280 100644 --- a/SoftLayer/CLI/block/options.py +++ b/SoftLayer/CLI/block/options.py @@ -8,7 +8,7 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import formatting -volume_size = ['20', '40', '80', '100', '250', '500', '1000', '2000-3000', '4000-7000', '8000-9000', '10000-12000'] +PACKAGE_STORAGE = 759 @click.command(cls=SLCommand) @@ -16,46 +16,32 @@ def cli(env): """List all options for ordering a block storage""" - network_manager = SoftLayer.NetworkManager(env.client) - datacenters = network_manager.get_datacenter() - - table = formatting.Table(['Name', 'Value'], title='Volume table') - - table.add_row(['Storage Type', 'performance,endurance']) - table.add_row(['Size (GB)', str(volume_size)]) - table.add_row(['OS Type', 'HYPER_V,LINUX,VMWARE,WINDOWS_2008,WINDOWS_GPT,WINDOWS,XEN']) - iops_table = formatting.Table(['Size (GB)', '20', '40', '80', '100', '250', '500', '1000', '2000-3000', - '4000-7000', '8000-9000', '10000-12000'], title='IOPS table') - snapshot_table = formatting.Table(['Storage Size (GB)', 'Available Snapshot Size (GB)'], title="Snapshot table") - - datacenter_str = ','.join([str(dc['longName']) for dc in datacenters]) - iops_table.add_row(['Size (GB)', *volume_size]) - iops_table.add_row(['Min IOPS', '100', '100', '100', '100', '100', '100', '100', '200', '300', '500', '1000']) - iops_table.add_row(['Max IOPS', '1000', '2000', '4000', '6000', '6000', '6000 or 10000', '6000 or 20000', - '6000 or 40000', '6000 or 48000', '6000 or 48000', '6000 or 48000']) - # table.add_row(['iops', iops_table]) - table.add_row(['Tier', '0.25,2,4,10']) - table.add_row(['location', datacenter_str]) - - snapshot_table.add_row(['20', '0,5,10,20']) - snapshot_table.add_row(['40', '0,5,10,20,40']) - snapshot_table.add_row(['80', '0,5,10,20,40,60,80']) - snapshot_table.add_row(['100', '0,5,10,20,40,60,80,100']) - snapshot_table.add_row(['250', '0,5,10,20,40,60,80,100,150,200,250']) - snapshot_table.add_row(['500', '0,5,10,20,40,60,80,100,150,200,250,300,350,400,450,500']) - snapshot_table.add_row(['1000', '0,5,10,20,40,60,80,100,150,200,250,300,350,400,450,500,600,700,1000']) - snapshot_table.add_row(['2000-3000', '0,5,10,20,40,60,80,100,150,200,250,300,350,400,450,500,600,700,1000,2000']) - snapshot_table.add_row( - ['4000-7000', '0,5,10,20,40,60,80,100,150,200,250,300,350,400,450,500,600,700,1000,2000,4000']) - snapshot_table.add_row( - ['8000-9000', '0,5,10,20,40,60,80,100,150,200,250,300,350,400,450,500,600,700,1000,2000,4000']) - snapshot_table.add_row( - ['10000-12000', '0,5,10,20,40,60,80,100,150,200,250,300,350,400,450,500,600,700,1000,2000,4000']) - - # table.add_row(['Snapshot Size (GB)', snapshot_table]) - table.add_row(['Note:', - 'IOPs limit above 6000 available in select data centers, refer to:' - 'http://knowledgelayer.softlayer.com/articles/new-ibm-block-and-file-storage-location-and-features']) - env.fout(table) + order_manager = SoftLayer.OrderingManager(env.client) + items = order_manager.get_items(PACKAGE_STORAGE) + datacenters = order_manager.get_regions(PACKAGE_STORAGE) + + iops_table = formatting.Table(['Id', 'Description', 'KeyName'], title='IOPS') + snapshot_table = formatting.Table(['Id', 'Description', 'KeyName'], title='Snapshot') + storage_table = formatting.Table(['Id', 'Description', 'KeyName'], title='Storage') + datacenter_table = formatting.Table(['Id', 'Description', 'KeyName'], title='Datacenter') + + for dc in datacenters: + datacenter_table.add_row([dc['location']['locationId'], dc.get('description'), dc['keyname']]) + + for item in items: + if item['itemCategory']['categoryCode'] == 'performance_storage_space': + storage_table.add_row([item.get('id'), item.get('description'), + item.get('keyName')]) + + if item['itemCategory']['categoryCode'] == 'storage_tier_level': + iops_table.add_row([item.get('id'), item.get('description'), + item.get('keyName')]) + + if item['itemCategory']['categoryCode'] == 'storage_snapshot_space': + snapshot_table.add_row([item.get('id'), item.get('description'), + item.get('keyName')]) + + env.fout(datacenter_table) env.fout(iops_table) + env.fout(storage_table) env.fout(snapshot_table) diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index 92b39102d..26bbed43d 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -6,7 +6,6 @@ :license: MIT, see LICENSE for more details. """ - from re import match from SoftLayer import exceptions @@ -717,3 +716,21 @@ def resolve_location_name(self, location_key): if location.get('regions', default_regions)[index_first].get('keyname') == location_key: return location_name raise exceptions.SoftLayerError("Location {} does not exist".format(location_key)) + + def get_items(self, package_id, storage_filter=None): + """"Returns the items . + + + :param int package_id: The package for which to get the items. + :param dict storage_filter: object filter. + """ + + return self.client.call('SoftLayer_Product_Package', 'getItems', filter=storage_filter, + id=package_id) + + def get_regions(self, package_id): + """returns the all regions. + + :param int package_id: The package for which to get the items. + """ + return self.client.call('SoftLayer_Product_Package', 'getRegions', id=package_id) diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index b25c42494..bbc2415fd 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -893,3 +893,11 @@ def test_issues1425_nonzeroterm(self): # Test None-existing price for term price_id = self.ordering.get_item_price_id("8", [price2, price1], 37) self.assertEqual(None, price_id) + + def test_get_items(self): + self.ordering.get_items(123) + self.assert_called_with('SoftLayer_Product_Package', 'getItems') + + def test_get_regions(self): + self.ordering.get_regions(123) + self.assert_called_with('SoftLayer_Product_Package', 'getRegions') From 78c02cc5a037f07ce4666dcc6e85674ef42a6608 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 11 Jul 2022 11:05:12 -0400 Subject: [PATCH 032/710] fix the tox analysis --- SoftLayer/CLI/block/options.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/block/options.py b/SoftLayer/CLI/block/options.py index 7c3a11280..7a020d68f 100644 --- a/SoftLayer/CLI/block/options.py +++ b/SoftLayer/CLI/block/options.py @@ -25,8 +25,10 @@ def cli(env): storage_table = formatting.Table(['Id', 'Description', 'KeyName'], title='Storage') datacenter_table = formatting.Table(['Id', 'Description', 'KeyName'], title='Datacenter') - for dc in datacenters: - datacenter_table.add_row([dc['location']['locationId'], dc.get('description'), dc['keyname']]) + for datacenter in datacenters: + datacenter_table.add_row([datacenter['location']['locationId'], + datacenter.get('description'), + datacenter['keyname']]) for item in items: if item['itemCategory']['categoryCode'] == 'performance_storage_space': From eede587d7a230a3b82713638d3e4fc24d06a0992 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 11 Jul 2022 13:12:05 -0500 Subject: [PATCH 033/710] #1686 added back support for --raw output format --- SoftLayer/CLI/formatting.py | 13 ++++++++----- tests/CLI/helper_tests.py | 6 ++++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/formatting.py b/SoftLayer/CLI/formatting.py index afe25ffff..69f7564d1 100644 --- a/SoftLayer/CLI/formatting.py +++ b/SoftLayer/CLI/formatting.py @@ -35,7 +35,7 @@ def format_output(data, fmt='table'): # pylint: disable=R0911,R0912 # responds to .prettytable() if hasattr(data, 'prettytable') and fmt in ('table', 'raw'): - return format_prettytable(data) + return format_prettytable(data, fmt) # responds to .to_python() if hasattr(data, 'to_python'): @@ -68,12 +68,12 @@ def format_output(data, fmt='table'): # pylint: disable=R0911,R0912 return str(data) -def format_prettytable(table): +def format_prettytable(table, fmt='table'): """Converts SoftLayer.CLI.formatting.Table instance to a prettytable.""" for i, row in enumerate(table.rows): for j, item in enumerate(row): table.rows[i][j] = format_output(item) - ptable = table.prettytable() + ptable = table.prettytable(fmt) return ptable @@ -258,9 +258,12 @@ def to_python(self): items.append(dict(zip(self.columns, formatted_row))) return items - def prettytable(self): + def prettytable(self, fmt='table'): """Returns a RICH table instance.""" - table = rTable(title=self.title, box=box.SQUARE, header_style="bright_cyan") + box_style = box.SQUARE + if fmt == 'raw': + box_style = None + table = rTable(title=self.title, box=box_style, header_style="bright_cyan") if self.sortby: try: # https://docs.python.org/3/howto/sorting.html#key-functions diff --git a/tests/CLI/helper_tests.py b/tests/CLI/helper_tests.py index cecb32932..cc87b3fe1 100644 --- a/tests/CLI/helper_tests.py +++ b/tests/CLI/helper_tests.py @@ -396,6 +396,12 @@ def test_format_output_table_invalid_sort(self): formatting.format_output, t, 'table', ) + def test_format_raw_table(self): + test_table = formatting.Table(['col1', 'col2']) + test_table.add_row(['row1', 'row2']) + pretty_table = formatting.format_output(test_table, 'raw') + self.assertIsNone(pretty_table.box) + class TestTemplateArgs(testing.TestCase): From f62ac35d396f4c86e6ac92e90d80eef2b51dac37 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 11 Jul 2022 13:28:25 -0500 Subject: [PATCH 034/710] #1686 set console width for output to pipe and files --- SoftLayer/CLI/environment.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SoftLayer/CLI/environment.py b/SoftLayer/CLI/environment.py index 0c4d17b22..d7b98501d 100644 --- a/SoftLayer/CLI/environment.py +++ b/SoftLayer/CLI/environment.py @@ -45,6 +45,10 @@ def __init__(self): def out(self, output): """Outputs a string to the console (stdout).""" + + # If we output to a | or file, need to set default width so all output is printed. + if not self.console.is_terminal: + self.console.width = 1000000 if self.format == 'json': try: self.console.print_json(output) From 87b902d33cd9bf70863d007c44568cd9214147de Mon Sep 17 00:00:00 2001 From: Brian Flores Date: Mon, 11 Jul 2022 18:02:46 -0400 Subject: [PATCH 035/710] updated hardware sensor command --- SoftLayer/CLI/hardware/sensor.py | 27 +++++++++++++++++------- SoftLayer/fixtures/SoftLayer_Hardware.py | 19 ++++++++++++++++- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/SoftLayer/CLI/hardware/sensor.py b/SoftLayer/CLI/hardware/sensor.py index 858d2584e..9f9d3da44 100644 --- a/SoftLayer/CLI/hardware/sensor.py +++ b/SoftLayer/CLI/hardware/sensor.py @@ -16,16 +16,16 @@ def cli(env, identifier, discrete): mgr = SoftLayer.HardwareManager(env.client) sensors = mgr.get_sensors(identifier) - temperature_table = formatting.Table(["Sensor", "Status", "Reading", "Min", "Max"], + temperature_table = formatting.Table(["Sensor", "Status", "Reading", "Critical Min", "Min", "Max", "Critical Max"], title='Temperature (c)') - volts_table = formatting.Table(["Sensor", "Status", "Reading", "Min", "Max"], + volts_table = formatting.Table(["Sensor", "Status", "Reading", "Critical Min", "Min", "Max", "Critical Max"], title='Volts') - watts_table = formatting.Table(["Sensor", "Status", "Reading"], + watts_table = formatting.Table(["Sensor", "Status", "Reading", "Critical Min", "Min", "Max", "Critical Max"], title='Watts') - rpm_table = formatting.Table(["Sensor", "Status", "Reading", "Min"], + rpm_table = formatting.Table(["Sensor", "Status", "Reading", "Critical Min", "Min", "Max", "Critical Max"], title='RPM') discrete_table = formatting.Table(["Sensor", "Status", "Reading"], @@ -36,26 +36,37 @@ def cli(env, identifier, discrete): temperature_table.add_row([sensor.get('sensorId'), sensor.get('status'), sensor.get('sensorReading'), + sensor.get('lowerCritical'), + sensor.get('lowerNonCritical'), sensor.get('upperNonCritical'), sensor.get('upperCritical')]) - if sensor.get('sensorUnits') == 'volts': + if sensor.get('sensorUnits') == 'Volts': volts_table.add_row([sensor.get('sensorId'), sensor.get('status'), sensor.get('sensorReading'), + sensor.get('lowerCritical'), sensor.get('lowerNonCritical'), - sensor.get('lowerCritical')]) + sensor.get('upperNonCritical'), + sensor.get('upperCritical')]) if sensor.get('sensorUnits') == 'Watts': watts_table.add_row([sensor.get('sensorId'), sensor.get('status'), - sensor.get('sensorReading')]) + sensor.get('sensorReading'), + sensor.get('lowerCritical'), + sensor.get('lowerNonCritical'), + sensor.get('upperNonCritical'), + sensor.get('upperCritical')]) if sensor.get('sensorUnits') == 'RPM': rpm_table.add_row([sensor.get('sensorId'), sensor.get('status'), sensor.get('sensorReading'), - sensor.get('lowerCritical')]) + sensor.get('lowerCritical'), + sensor.get('lowerNonCritical'), + sensor.get('upperNonCritical'), + sensor.get('upperCritical')]) if sensor.get('sensorUnits') == 'discrete': discrete_table.add_row([sensor.get('sensorId'), diff --git a/SoftLayer/fixtures/SoftLayer_Hardware.py b/SoftLayer/fixtures/SoftLayer_Hardware.py index 3c74dc439..f81644539 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware.py @@ -62,6 +62,8 @@ getSensorData = [ { + "lowerCritical": "5.000", + "lowerNonCritical": "10.000", "sensorId": "Ambient 1 Temperature", "sensorReading": "25.000", "sensorUnits": "degrees C", @@ -72,10 +74,13 @@ }, { "lowerCritical": "3500.000", + "lowerNonCritical": "3700.000", "sensorId": "Fan 1 Tach", "sensorReading": "6580.000", "sensorUnits": "RPM", - "status": "ok" + "status": "ok", + "upperCritical": "25400.000", + "upperNonCritical": "25300.000", }, { "sensorId": "IPMI Watchdog", "sensorReading": "0x0", @@ -86,4 +91,16 @@ "sensorReading": "70.000", "sensorUnits": "Watts", "status": "ok" + }, + { + "lowerCritical": "10.536", + "lowerNonCritical": "10.780", + "lowerNonRecoverable": "10.170", + "sensorId": "12V", + "sensorReading": "12.305", + "sensorUnits": "Volts", + "status": "ok", + "upperCritical": "13.281", + "upperNonCritical": "12.915", + "upperNonRecoverable": "13.403" }] From 98222e02a11587c1e4f72ee5dc963b900c7899c9 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 12 Jul 2022 09:39:04 -0400 Subject: [PATCH 036/710] fix the team code review comments --- SoftLayer/CLI/file/options.py | 73 ++++++++++++++------------------ SoftLayer/managers/ordering.py | 20 ++++++++- tests/managers/ordering_tests.py | 14 ++++-- 3 files changed, 61 insertions(+), 46 deletions(-) diff --git a/SoftLayer/CLI/file/options.py b/SoftLayer/CLI/file/options.py index 68f5f05fe..f2ca734b9 100644 --- a/SoftLayer/CLI/file/options.py +++ b/SoftLayer/CLI/file/options.py @@ -8,7 +8,8 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import formatting -volume_size = ['20', '40', '80', '100', '250', '500', '1000', '2000-3000', '4000-7000', '8000-9000', '10000-12000'] + +PACKAGE_STORAGE = 759 @click.command(cls=SLCommand) @@ -16,46 +17,34 @@ def cli(env): """List all options for ordering a file storage""" - network_manager = SoftLayer.NetworkManager(env.client) - datacenters = network_manager.get_datacenter() - - table = formatting.Table(['Name', 'Value'], title='Volume table') - - table.add_row(['Storage Type', 'performance,endurance']) - table.add_row(['Size (GB)', str(volume_size)]) - - iops_table = formatting.Table(['Size (GB)', '20', '40', '80', '100', '250', '500', '1000', '2000-3000', - '4000-7000', '8000-9000', '10000-12000'], title='IOPS table') - snapshot_table = formatting.Table(['Storage Size (GB)', 'Available Snapshot Size (GB)'], title="Snapshot table") - - datacenter_str = ','.join([str(dc['longName']) for dc in datacenters]) - iops_table.add_row(['Size (GB)', *volume_size]) - iops_table.add_row(['Min IOPS', '100', '100', '100', '100', '100', '100', '100', '200', '300', '500', '1000']) - iops_table.add_row(['Max IOPS', '1000', '2000', '4000', '6000', '6000', '6000 or 10000', '6000 or 20000', - '6000 or 40000', '6000 or 48000', '6000 or 48000', '6000 or 48000']) - # table.add_row(['iops', iops_table]) - table.add_row(['Tier', '0.25,2,4,10']) - table.add_row(['location', datacenter_str]) - - snapshot_table.add_row(['20', '0,5,10,20']) - snapshot_table.add_row(['40', '0,5,10,20,40']) - snapshot_table.add_row(['80', '0,5,10,20,40,60,80']) - snapshot_table.add_row(['100', '0,5,10,20,40,60,80,100']) - snapshot_table.add_row(['250', '0,5,10,20,40,60,80,100,150,200,250']) - snapshot_table.add_row(['500', '0,5,10,20,40,60,80,100,150,200,250,300,350,400,450,500']) - snapshot_table.add_row(['1000', '0,5,10,20,40,60,80,100,150,200,250,300,350,400,450,500,600,700,1000']) - snapshot_table.add_row(['2000-3000', '0,5,10,20,40,60,80,100,150,200,250,300,350,400,450,500,600,700,1000,2000']) - snapshot_table.add_row( - ['4000-7000', '0,5,10,20,40,60,80,100,150,200,250,300,350,400,450,500,600,700,1000,2000,4000']) - snapshot_table.add_row( - ['8000-9000', '0,5,10,20,40,60,80,100,150,200,250,300,350,400,450,500,600,700,1000,2000,4000']) - snapshot_table.add_row( - ['10000-12000', '0,5,10,20,40,60,80,100,150,200,250,300,350,400,450,500,600,700,1000,2000,4000']) - - # table.add_row(['Snapshot Size (GB)', snapshot_table]) - table.add_row(['Note:', - 'IOPs limit above 6000 available in select data centers, refer to:' - 'http://knowledgelayer.softlayer.com/articles/new-ibm-block-and-file-storage-location-and-features']) - env.fout(table) + order_manager = SoftLayer.OrderingManager(env.client) + items = order_manager.get_items(PACKAGE_STORAGE) + datacenters = order_manager.get_regions(PACKAGE_STORAGE) + + iops_table = formatting.Table(['Id', 'Description', 'KeyName'], title='IOPS') + snapshot_table = formatting.Table(['Id', 'Description', 'KeyName'], title='Snapshot') + storage_table = formatting.Table(['Id', 'Description', 'KeyName'], title='Storage') + datacenter_table = formatting.Table(['Id', 'Description', 'KeyName'], title='Datacenter') + + for datacenter in datacenters: + datacenter_table.add_row([datacenter['location']['locationId'], + datacenter.get('description'), + datacenter['keyname']]) + + for item in items: + if item['itemCategory']['categoryCode'] == 'performance_storage_space': + storage_table.add_row([item.get('id'), item.get('description'), + item.get('keyName')]) + + if item['itemCategory']['categoryCode'] == 'storage_tier_level': + iops_table.add_row([item.get('id'), item.get('description'), + item.get('keyName')]) + + if item['itemCategory']['categoryCode'] == 'storage_snapshot_space': + snapshot_table.add_row([item.get('id'), item.get('description'), + item.get('keyName')]) + + env.fout(datacenter_table) env.fout(iops_table) + env.fout(storage_table) env.fout(snapshot_table) diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index 92b39102d..8adcd2544 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -6,7 +6,6 @@ :license: MIT, see LICENSE for more details. """ - from re import match from SoftLayer import exceptions @@ -717,3 +716,22 @@ def resolve_location_name(self, location_key): if location.get('regions', default_regions)[index_first].get('keyname') == location_key: return location_name raise exceptions.SoftLayerError("Location {} does not exist".format(location_key)) + + def get_items(self, package_id, storage_filter=None): + """"Returns the items . + + + :param int package_id: The package for which to get the items. + :param dict storage_filter: object filter. + """ + + return self.client.call('SoftLayer_Product_Package', 'getItems', filter=storage_filter, + id=package_id) + + def get_regions(self, package_id): + """returns the all regions. + + + :param int package_id: The package for which to get the items. + """ + return self.client.call('SoftLayer_Product_Package', 'getRegions', id=package_id) diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index b25c42494..063dbcdd5 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -744,7 +744,7 @@ def test_get_item_capacity_core(self): "capacity": "1", "id": 10201, "keyName": "GUEST_CORE_1_DEDICATED", - }] + }] item_capacity = self.ordering.get_item_capacity(items, ['GUEST_CORE_1_DEDICATED', 'OS_RHEL_7_X_LAMP_64_BIT']) @@ -761,7 +761,7 @@ def test_get_item_capacity_storage(self): "capacity": "1", "id": 10201, "keyName": "READHEAVY_TIER", - }] + }] item_capacity = self.ordering.get_item_capacity(items, ['READHEAVY_TIER', 'STORAGE_SPACE_FOR_2_IOPS_PER_GB']) @@ -779,7 +779,7 @@ def test_get_item_capacity_intel(self): "capacity": "1", "id": 10201, "keyName": "GUEST_CORE_1_DEDICATED", - }] + }] item_capacity = self.ordering.get_item_capacity(items, ['INTEL_XEON_2690_2_60', 'BANDWIDTH_20000_GB']) @@ -893,3 +893,11 @@ def test_issues1425_nonzeroterm(self): # Test None-existing price for term price_id = self.ordering.get_item_price_id("8", [price2, price1], 37) self.assertEqual(None, price_id) + + def test_get_items(self): + self.ordering.get_items(123) + self.assert_called_with('SoftLayer_Product_Package', 'getItems') + + def test_get_regions(self): + self.ordering.get_regions(123) + self.assert_called_with('SoftLayer_Product_Package', 'getRegions') From aea1a6e11122070b756c00f53f0dc581899d4baa Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 13 Jul 2022 09:51:33 -0500 Subject: [PATCH 037/710] removed bad debugging print statement --- SoftLayer/CLI/formatting.py | 1 - 1 file changed, 1 deletion(-) diff --git a/SoftLayer/CLI/formatting.py b/SoftLayer/CLI/formatting.py index 69f7564d1..4cfe9cd96 100644 --- a/SoftLayer/CLI/formatting.py +++ b/SoftLayer/CLI/formatting.py @@ -208,7 +208,6 @@ def to_python(self): return self def __str__(self): - print("CASTSDFSDFSDFSDFSDF") return self.separator.join(str(x) for x in self) From 7700a069aebc61eb2f4999cdbfa525dccdd05c55 Mon Sep 17 00:00:00 2001 From: edsonarios Date: Wed, 13 Jul 2022 11:41:39 -0400 Subject: [PATCH 038/710] Improved successful response to command - slcli vs cancel --- SoftLayer/CLI/virt/cancel.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/virt/cancel.py b/SoftLayer/CLI/virt/cancel.py index 4d394554d..9131aa788 100644 --- a/SoftLayer/CLI/virt/cancel.py +++ b/SoftLayer/CLI/virt/cancel.py @@ -21,4 +21,7 @@ def cli(env, identifier): if not (env.skip_confirmations or formatting.no_going_back(vs_id)): raise exceptions.CLIAbort('Aborted') - vsi.cancel_instance(vs_id) + vs = vsi.cancel_instance(vs_id) + + if vs: + env.fout("The virtual server instance: {} was cancelled.".format(vs_id)) From 6839f776d19c9f7fea888b5f815505fa11469a56 Mon Sep 17 00:00:00 2001 From: edsonarios Date: Wed, 13 Jul 2022 11:53:56 -0400 Subject: [PATCH 039/710] Improved name variable --- SoftLayer/CLI/virt/cancel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/virt/cancel.py b/SoftLayer/CLI/virt/cancel.py index 9131aa788..db1bbfc48 100644 --- a/SoftLayer/CLI/virt/cancel.py +++ b/SoftLayer/CLI/virt/cancel.py @@ -21,7 +21,7 @@ def cli(env, identifier): if not (env.skip_confirmations or formatting.no_going_back(vs_id)): raise exceptions.CLIAbort('Aborted') - vs = vsi.cancel_instance(vs_id) + virtual_server = vsi.cancel_instance(vs_id) - if vs: + if virtual_server: env.fout("The virtual server instance: {} was cancelled.".format(vs_id)) From 02b8d6a9f79491d5118e62879528017a45c0102e Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 14 Jul 2022 13:57:30 -0500 Subject: [PATCH 040/710] Fixed an issue where printing an empty list would cause Rich.Table to crash #1658 --- SoftLayer/CLI/formatting.py | 7 ++++++- tests/CLI/helper_tests.py | 10 ++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/formatting.py b/SoftLayer/CLI/formatting.py index 4cfe9cd96..02781fcf5 100644 --- a/SoftLayer/CLI/formatting.py +++ b/SoftLayer/CLI/formatting.py @@ -72,7 +72,12 @@ def format_prettytable(table, fmt='table'): """Converts SoftLayer.CLI.formatting.Table instance to a prettytable.""" for i, row in enumerate(table.rows): for j, item in enumerate(row): - table.rows[i][j] = format_output(item) + # Issue when adding items that evaulate to None (like empty lists) for Rich Tables + # so we just cast those to a str + if item: + table.rows[i][j] = format_output(item) + else: + table.rows[i][j] = str(item) ptable = table.prettytable(fmt) return ptable diff --git a/tests/CLI/helper_tests.py b/tests/CLI/helper_tests.py index cc87b3fe1..1c8c2d91d 100644 --- a/tests/CLI/helper_tests.py +++ b/tests/CLI/helper_tests.py @@ -484,3 +484,13 @@ def test_format_api_list_with_none_value(self): self.assertIsInstance(result, formatting.Table) self.assertEqual(result.columns, ['key']) + + def test_format_api_list_with_empty_array(self): + result = formatting.iter_to_table([{'id': 130224450, 'activeTickets': []}]) + self.assertIsInstance(result, formatting.Table) + self.assertIn('id', result.columns) + self.assertIn('activeTickets', result.columns) + formatted = formatting.format_output(result, "table") + # No good ways to test whats actually in a Rich.Table without going through the hassel of + # printing it out. As long as this didn't throw and exception it should be fine. + self.assertEqual(formatted.row_count, 1) From c7d6cf5f11b0fbd3a146d8048c3145688c07a4b4 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 18 Jul 2022 09:58:29 -0400 Subject: [PATCH 041/710] New command ipsec order --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/vpn/ipsec/order.py | 29 +++++++++++ SoftLayer/fixtures/SoftLayer_Product_Order.py | 18 +++++++ .../fixtures/SoftLayer_Product_Package.py | 52 ++++++++++++++++++- SoftLayer/managers/ipsec.py | 15 ++++++ docs/cli/ipsec.rst | 4 ++ tests/CLI/modules/ipsec_tests.py | 12 +++++ 7 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 SoftLayer/CLI/vpn/ipsec/order.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 75e42102e..10f82273c 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -210,6 +210,7 @@ ('ipsec:translation-remove', 'SoftLayer.CLI.vpn.ipsec.translation.remove:cli'), ('ipsec:translation-update', 'SoftLayer.CLI.vpn.ipsec.translation.update:cli'), ('ipsec:update', 'SoftLayer.CLI.vpn.ipsec.update:cli'), + ('ipsec:order', 'SoftLayer.CLI.vpn.ipsec.order:cli'), ('loadbal', 'SoftLayer.CLI.loadbal'), ('loadbal:detail', 'SoftLayer.CLI.loadbal.detail:cli'), diff --git a/SoftLayer/CLI/vpn/ipsec/order.py b/SoftLayer/CLI/vpn/ipsec/order.py new file mode 100644 index 000000000..9681c2809 --- /dev/null +++ b/SoftLayer/CLI/vpn/ipsec/order.py @@ -0,0 +1,29 @@ +"""Order a IPSec VPN tunnel.""" +# :licenses: MIT, see LICENSE for more details. + +import click + +import SoftLayer + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +@click.command(cls=SoftLayer.CLI.command.SLCommand, ) +@click.option('--datacenter', '-d', required=True, prompt=True, help="Datacenter shortname") +@environment.pass_env +def cli(env, datacenter): + """Order/create a IPSec VPN tunnel instance.""" + + ipsec_manager = SoftLayer.IPSECManager(env.client) + + result = ipsec_manager.order(datacenter, ['IPSEC_STANDARD']) + + table = formatting.KeyValueTable(['Name', 'Value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + table.add_row(['Id', result['orderId']]) + table.add_row(['Created', result['orderDate']]) + table.add_row(['Name', result['placedOrder']['items'][0]['description']]) + + env.fout(table) diff --git a/SoftLayer/fixtures/SoftLayer_Product_Order.py b/SoftLayer/fixtures/SoftLayer_Product_Order.py index b0d67d868..b5d2dada7 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Order.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Order.py @@ -303,3 +303,21 @@ "id": 1071, "keyName": "PUBLIC_NETWORK_VLAN"}} ]} + +ipsec_placeOrder = { + "orderDate": "2022-07-14T16:09:08-06:00", + "orderId": 123456, + "placedOrder": { + "items": [ + { + "categoryCode": "network_tunnel", + "description": "IPSEC - Standard", + "id": 931479898, + "itemId": 1092, + "itemPriceId": "2048", + + } + ] + + } +} diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py index d2a93d89c..cc97e834e 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py @@ -851,7 +851,12 @@ "keyName": "LOAD_BALANCER_UPTIME", } }] -}] +}, { + "firstOrderStepId": 1, + "id": 0, + "isActive": 1, + "keyName": "ADDITIONAL_PRODUCTS", + "name": "Additional Products"}] getItems = [ { @@ -2171,3 +2176,48 @@ }] } ] + +getAllObjectsIPSEC = [{ + "firstOrderStepId": 1, + "id": 0, + "isActive": 1, + "keyName": "ADDITIONAL_PRODUCTS", + "name": "Additional Products"}] + +getItems_IPSEC = [{ + "description": "IPSEC - Standard", + "id": 1092, + "keyName": "IPSEC_STANDARD", + "categories": [ + { + "categoryCode": "network_tunnel", + "id": 117, + "name": "Network Tunnel", + "quantityLimit": 0, + }, + { + "categoryCode": "one_time_charge", + "id": 33, + "name": "One Time Charge", + "quantityLimit": 0, + } + ], + "itemCategory": { + "categoryCode": "network_tunnel", + "id": 117, + "name": "Network Tunnel", + "quantityLimit": 0, + }, + "prices": [ + { + "id": 51379, + "itemId": 1092, + "laborFee": "0", + "locationGroupId": 503, + "oneTimeFee": "0", + "recurringFee": "102", + "setupFee": "0", + "sort": 0, + } + ] +}] diff --git a/SoftLayer/managers/ipsec.py b/SoftLayer/managers/ipsec.py index 29317516e..c72f50468 100644 --- a/SoftLayer/managers/ipsec.py +++ b/SoftLayer/managers/ipsec.py @@ -7,6 +7,7 @@ """ from SoftLayer.exceptions import SoftLayerAPIError +from SoftLayer.managers import ordering from SoftLayer import utils @@ -284,3 +285,17 @@ def update_tunnel_context(self, context_id, friendly_name=None, if phase2_key_ttl is not None: context['phaseTwoKeylife'] = phase2_key_ttl return self.context.editObject(context, id=context_id) + + def order(self, datacenter, item_package): + """Create a license + + :param string datacenter: the datacenter shortname + :param string[] item_package: items array + """ + complex_type = 'SoftLayer_Container_Product_Order_Network_Tunnel_Ipsec' + ordering_manager = ordering.OrderingManager(self.client) + return ordering_manager.place_order(package_keyname='ADDITIONAL_PRODUCTS', + location=datacenter, + item_keynames=item_package, + complex_type=complex_type, + hourly=False) diff --git a/docs/cli/ipsec.rst b/docs/cli/ipsec.rst index cc1ed5b11..8bfb9905f 100644 --- a/docs/cli/ipsec.rst +++ b/docs/cli/ipsec.rst @@ -272,3 +272,7 @@ The following is an example of updating an existing address translation entry. $ slcli ipsec translation-update 445 --translation-id 15924 --static-ip 10.1.249.86 --remote-ip 50.100.0.8 --note 'new email server' Updated translation #15924 + +.. click:: SoftLayer.CLI.vpn.ipsec.order:cli + :prog: ipsec order + :show-nested: \ No newline at end of file diff --git a/tests/CLI/modules/ipsec_tests.py b/tests/CLI/modules/ipsec_tests.py index 777df8b90..5a7a6589b 100644 --- a/tests/CLI/modules/ipsec_tests.py +++ b/tests/CLI/modules/ipsec_tests.py @@ -7,8 +7,11 @@ import json + from SoftLayer.CLI.exceptions import ArgumentError from SoftLayer.CLI.exceptions import CLIHalt +from SoftLayer.fixtures import SoftLayer_Product_Order +from SoftLayer.fixtures import SoftLayer_Product_Package from SoftLayer import testing from SoftLayer import utils @@ -508,3 +511,12 @@ def test_ipsec_translation_update(self): 'internalIpAddress': '10.50.0.1', 'customerIpAddress': '50.50.0.1', 'notes': 'lost'},)) + + def test_ipsec_order(self): + _mock = self.set_mock('SoftLayer_Product_Package', 'getItems') + _mock.return_value = SoftLayer_Product_Package.getItems_IPSEC + + order_mock = self.set_mock('SoftLayer_Product_Order', 'placeOrder') + order_mock.return_value = SoftLayer_Product_Order.ipsec_placeOrder + result = self.run_command(['ipsec', 'order', '-d', 'dal13']) + self.assert_no_fail(result) From 8653b07f7d97f085efa9670bb3d01257b92d36de Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 18 Jul 2022 10:35:58 -0400 Subject: [PATCH 042/710] fix the some code --- SoftLayer/managers/ipsec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/managers/ipsec.py b/SoftLayer/managers/ipsec.py index c72f50468..d79acbf68 100644 --- a/SoftLayer/managers/ipsec.py +++ b/SoftLayer/managers/ipsec.py @@ -287,7 +287,7 @@ def update_tunnel_context(self, context_id, friendly_name=None, return self.context.editObject(context, id=context_id) def order(self, datacenter, item_package): - """Create a license + """Create a ipsec :param string datacenter: the datacenter shortname :param string[] item_package: items array From cce8e11feb6bf1e12d12ccec789d48938803c264 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 18 Jul 2022 10:46:13 -0400 Subject: [PATCH 043/710] fix the problems --- SoftLayer/fixtures/SoftLayer_Product_Package.py | 7 +------ SoftLayer/managers/ipsec.py | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py index cc97e834e..8ca1c4085 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py @@ -851,12 +851,7 @@ "keyName": "LOAD_BALANCER_UPTIME", } }] -}, { - "firstOrderStepId": 1, - "id": 0, - "isActive": 1, - "keyName": "ADDITIONAL_PRODUCTS", - "name": "Additional Products"}] +}] getItems = [ { diff --git a/SoftLayer/managers/ipsec.py b/SoftLayer/managers/ipsec.py index d79acbf68..1043b2f1d 100644 --- a/SoftLayer/managers/ipsec.py +++ b/SoftLayer/managers/ipsec.py @@ -287,7 +287,7 @@ def update_tunnel_context(self, context_id, friendly_name=None, return self.context.editObject(context, id=context_id) def order(self, datacenter, item_package): - """Create a ipsec + """Create a ipsec. :param string datacenter: the datacenter shortname :param string[] item_package: items array From 41509055b7e3268bbd82bafdb8100595ca875fd4 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 18 Jul 2022 10:10:08 -0500 Subject: [PATCH 044/710] Added a dependabot scanner yaml file #1529 --- .github/dependabot.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..e74b1b6ed --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +# Use `allow` to specify which dependencies to maintain +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-update + +version: 2 +updates: + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "weekly" + commit-message: + prefix: "pip prod" + prefix-development: "pip dev" + include: "scope" \ No newline at end of file From da9fdc60d7ff0895c7ab3a2a10ddd19ced5f6e4a Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 19 Jul 2022 11:11:19 -0400 Subject: [PATCH 045/710] block|file volume-options improvements --- SoftLayer/CLI/block/options.py | 7 +++++-- SoftLayer/CLI/file/options.py | 12 +++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/SoftLayer/CLI/block/options.py b/SoftLayer/CLI/block/options.py index 7a020d68f..e42e5fbc3 100644 --- a/SoftLayer/CLI/block/options.py +++ b/SoftLayer/CLI/block/options.py @@ -22,9 +22,12 @@ def cli(env): iops_table = formatting.Table(['Id', 'Description', 'KeyName'], title='IOPS') snapshot_table = formatting.Table(['Id', 'Description', 'KeyName'], title='Snapshot') - storage_table = formatting.Table(['Id', 'Description', 'KeyName'], title='Storage') + storage_table = formatting.Table(['Id', 'Description', 'KeyName', 'Capacity Minimum'], title='Storage') datacenter_table = formatting.Table(['Id', 'Description', 'KeyName'], title='Datacenter') + storage_table.align['Description'] = 'l' + storage_table.align['KeyName'] = 'l' + storage_table.sortby = 'Id' for datacenter in datacenters: datacenter_table.add_row([datacenter['location']['locationId'], datacenter.get('description'), @@ -33,7 +36,7 @@ def cli(env): for item in items: if item['itemCategory']['categoryCode'] == 'performance_storage_space': storage_table.add_row([item.get('id'), item.get('description'), - item.get('keyName')]) + item.get('keyName'), item.get('capacityMinimum') or '-']) if item['itemCategory']['categoryCode'] == 'storage_tier_level': iops_table.add_row([item.get('id'), item.get('description'), diff --git a/SoftLayer/CLI/file/options.py b/SoftLayer/CLI/file/options.py index f2ca734b9..dfaa3f1ed 100644 --- a/SoftLayer/CLI/file/options.py +++ b/SoftLayer/CLI/file/options.py @@ -8,7 +8,6 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import formatting - PACKAGE_STORAGE = 759 @@ -23,9 +22,12 @@ def cli(env): iops_table = formatting.Table(['Id', 'Description', 'KeyName'], title='IOPS') snapshot_table = formatting.Table(['Id', 'Description', 'KeyName'], title='Snapshot') - storage_table = formatting.Table(['Id', 'Description', 'KeyName'], title='Storage') + file_storage_table = formatting.Table(['Id', 'Description', 'KeyName', 'Capacity Minimum'], title='Storage') datacenter_table = formatting.Table(['Id', 'Description', 'KeyName'], title='Datacenter') + file_storage_table.align['Description'] = 'l' + file_storage_table.align['KeyName'] = 'l' + file_storage_table.sortby = 'Id' for datacenter in datacenters: datacenter_table.add_row([datacenter['location']['locationId'], datacenter.get('description'), @@ -33,8 +35,8 @@ def cli(env): for item in items: if item['itemCategory']['categoryCode'] == 'performance_storage_space': - storage_table.add_row([item.get('id'), item.get('description'), - item.get('keyName')]) + file_storage_table.add_row([item.get('id'), item.get('description'), + item.get('keyName'), item.get('capacityMinimum') or '-']) if item['itemCategory']['categoryCode'] == 'storage_tier_level': iops_table.add_row([item.get('id'), item.get('description'), @@ -46,5 +48,5 @@ def cli(env): env.fout(datacenter_table) env.fout(iops_table) - env.fout(storage_table) + env.fout(file_storage_table) env.fout(snapshot_table) From 59fd14dec8523c2e42d9ed5ca2b7939d226a7d2a Mon Sep 17 00:00:00 2001 From: edsonarios Date: Wed, 20 Jul 2022 14:00:25 -0400 Subject: [PATCH 046/710] Change form to print the multiples tables --- SoftLayer/CLI/dedicatedhost/create_options.py | 2 +- SoftLayer/CLI/hardware/create_options.py | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/dedicatedhost/create_options.py b/SoftLayer/CLI/dedicatedhost/create_options.py index b81677693..8ad5aafcd 100644 --- a/SoftLayer/CLI/dedicatedhost/create_options.py +++ b/SoftLayer/CLI/dedicatedhost/create_options.py @@ -58,4 +58,4 @@ def cli(env, **kwargs): br_table.add_row([router['hostname']]) tables.append(br_table) - env.fout(formatting.listing(tables, separator='\n')) + env.fout(tables) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index 8846f4ee1..9fbf7c2f5 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -51,9 +51,7 @@ def cli(env, prices, location=None): tables.append(_port_speed_prices_table(options['port_speeds'], prices)) tables.append(_extras_prices_table(options['extras'], prices)) tables.append(_get_routers(routers)) - - # since this is multiple tables, this is required for a valid JSON object to be rendered. - env.fout(formatting.listing(tables, separator='\n')) + env.fout(tables) def _preset_prices_table(sizes, prices=False): From 926f481405e6fc5fca6e26f8092e98eb6227c911 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 20 Jul 2022 15:32:44 -0400 Subject: [PATCH 047/710] block/file volume-options improvements 2 --- SoftLayer/CLI/block/options.py | 16 +++++++++++++-- SoftLayer/CLI/file/options.py | 36 ++++++++++++++++++++++------------ 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/SoftLayer/CLI/block/options.py b/SoftLayer/CLI/block/options.py index 7a020d68f..4e47d0dd6 100644 --- a/SoftLayer/CLI/block/options.py +++ b/SoftLayer/CLI/block/options.py @@ -20,15 +20,27 @@ def cli(env): items = order_manager.get_items(PACKAGE_STORAGE) datacenters = order_manager.get_regions(PACKAGE_STORAGE) + network = SoftLayer.NetworkManager(env.client) + + pods = network.get_closed_pods() + iops_table = formatting.Table(['Id', 'Description', 'KeyName'], title='IOPS') snapshot_table = formatting.Table(['Id', 'Description', 'KeyName'], title='Snapshot') storage_table = formatting.Table(['Id', 'Description', 'KeyName'], title='Storage') - datacenter_table = formatting.Table(['Id', 'Description', 'KeyName'], title='Datacenter') + datacenter_table = formatting.Table(['Id', 'Description', 'KeyName', 'Notes'], title='Datacenter') for datacenter in datacenters: + closure = [] + for pod in pods: + if datacenter['location']['location']['name'] in str(pod['name']): + closure.append(pod['name']) + + notes = '-' + if len(closure) > 0: + notes = 'closed soon: %s' % (', '.join(closure)) datacenter_table.add_row([datacenter['location']['locationId'], datacenter.get('description'), - datacenter['keyname']]) + datacenter['keyname'], notes]) for item in items: if item['itemCategory']['categoryCode'] == 'performance_storage_space': diff --git a/SoftLayer/CLI/file/options.py b/SoftLayer/CLI/file/options.py index f2ca734b9..cbb2bd0d1 100644 --- a/SoftLayer/CLI/file/options.py +++ b/SoftLayer/CLI/file/options.py @@ -21,28 +21,40 @@ def cli(env): items = order_manager.get_items(PACKAGE_STORAGE) datacenters = order_manager.get_regions(PACKAGE_STORAGE) + network = SoftLayer.NetworkManager(env.client) + + pods = network.get_closed_pods() + iops_table = formatting.Table(['Id', 'Description', 'KeyName'], title='IOPS') snapshot_table = formatting.Table(['Id', 'Description', 'KeyName'], title='Snapshot') storage_table = formatting.Table(['Id', 'Description', 'KeyName'], title='Storage') - datacenter_table = formatting.Table(['Id', 'Description', 'KeyName'], title='Datacenter') + datacenter_table = formatting.Table(['Id', 'Description', 'KeyName', 'Notes'], title='Datacenter') for datacenter in datacenters: + closure = [] + for pod in pods: + if datacenter['location']['location']['name'] in str(pod['name']): + closure.append(pod['name']) + + notes = '-' + if len(closure) > 0: + notes = 'closed soon: %s' % (', '.join(closure)) datacenter_table.add_row([datacenter['location']['locationId'], datacenter.get('description'), - datacenter['keyname']]) + datacenter['keyname'], notes]) - for item in items: - if item['itemCategory']['categoryCode'] == 'performance_storage_space': - storage_table.add_row([item.get('id'), item.get('description'), - item.get('keyName')]) + for item_file in items: + if item_file['itemCategory']['categoryCode'] == 'performance_storage_space': + storage_table.add_row([item_file.get('id'), item_file.get('description'), + item_file.get('keyName')]) - if item['itemCategory']['categoryCode'] == 'storage_tier_level': - iops_table.add_row([item.get('id'), item.get('description'), - item.get('keyName')]) + if item_file['itemCategory']['categoryCode'] == 'storage_tier_level': + iops_table.add_row([item_file.get('id'), item_file.get('description'), + item_file.get('keyName')]) - if item['itemCategory']['categoryCode'] == 'storage_snapshot_space': - snapshot_table.add_row([item.get('id'), item.get('description'), - item.get('keyName')]) + if item_file['itemCategory']['categoryCode'] == 'storage_snapshot_space': + snapshot_table.add_row([item_file.get('id'), item_file.get('description'), + item_file.get('keyName')]) env.fout(datacenter_table) env.fout(iops_table) From 3779f8d89456a983f7ffdbe7a9931a81e6b6498e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Jul 2022 15:33:36 +0000 Subject: [PATCH 048/710] pip prod(deps): bump rich from 12.3.0 to 12.5.1 Bumps [rich](https://github.com/willmcgugan/rich) from 12.3.0 to 12.5.1. - [Release notes](https://github.com/willmcgugan/rich/releases) - [Changelog](https://github.com/Textualize/rich/blob/master/CHANGELOG.md) - [Commits](https://github.com/willmcgugan/rich/compare/v12.3.0...v12.5.1) --- updated-dependencies: - dependency-name: rich dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- tools/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 4fc80e4a9..a7578171e 100644 --- a/setup.py +++ b/setup.py @@ -40,7 +40,7 @@ 'prompt_toolkit >= 2', 'pygments >= 2.0.0', 'urllib3 >= 1.24', - 'rich == 12.3.0' + 'rich == 12.5.1' ], keywords=['softlayer', 'cloud', 'slcli'], classifiers=[ diff --git a/tools/requirements.txt b/tools/requirements.txt index 4018389ba..31e85ef47 100644 --- a/tools/requirements.txt +++ b/tools/requirements.txt @@ -4,6 +4,6 @@ requests >= 2.20.0 prompt_toolkit >= 2 pygments >= 2.0.0 urllib3 >= 1.24 -rich == 12.3.0 +rich == 12.5.1 # only used for soap transport # softlayer-zeep >= 5.0.0 From fdd1f6dc02ff098048b647cac3f71f6e78c2c72b Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 22 Jul 2022 17:22:00 -0400 Subject: [PATCH 049/710] block/file volume-options improvement 3 --- SoftLayer/CLI/block/options.py | 86 ++++++++++++++++++++++++--------- SoftLayer/CLI/file/options.py | 88 +++++++++++++++++++++++++--------- SoftLayer/managers/ordering.py | 7 ++- 3 files changed, 134 insertions(+), 47 deletions(-) diff --git a/SoftLayer/CLI/block/options.py b/SoftLayer/CLI/block/options.py index e42e5fbc3..a3a73fdc6 100644 --- a/SoftLayer/CLI/block/options.py +++ b/SoftLayer/CLI/block/options.py @@ -12,41 +12,83 @@ @click.command(cls=SLCommand) +@click.argument('location', required=False) +@click.option('--prices', '-p', is_flag=True, + help='Use --prices to list the server item prices, and to list the Item Prices by location,' + 'add it to the --prices option using location short name, e.g. --prices dal13') @environment.pass_env -def cli(env): +def cli(env, prices, location=None): """List all options for ordering a block storage""" order_manager = SoftLayer.OrderingManager(env.client) items = order_manager.get_items(PACKAGE_STORAGE) - datacenters = order_manager.get_regions(PACKAGE_STORAGE) + datacenters = order_manager.get_regions(PACKAGE_STORAGE, location) - iops_table = formatting.Table(['Id', 'Description', 'KeyName'], title='IOPS') - snapshot_table = formatting.Table(['Id', 'Description', 'KeyName'], title='Snapshot') - storage_table = formatting.Table(['Id', 'Description', 'KeyName', 'Capacity Minimum'], title='Storage') + tables = [] datacenter_table = formatting.Table(['Id', 'Description', 'KeyName'], title='Datacenter') - storage_table.align['Description'] = 'l' - storage_table.align['KeyName'] = 'l' - storage_table.sortby = 'Id' for datacenter in datacenters: datacenter_table.add_row([datacenter['location']['locationId'], datacenter.get('description'), datacenter['keyname']]) - for item in items: - if item['itemCategory']['categoryCode'] == 'performance_storage_space': - storage_table.add_row([item.get('id'), item.get('description'), - item.get('keyName'), item.get('capacityMinimum') or '-']) + tables.append(datacenter_table) + tables.append(_ios_get_table(items, prices)) + tables.append(_storage_table(items, prices)) + tables.append(_snapshot_get_table(items, prices)) + env.fout(tables) - if item['itemCategory']['categoryCode'] == 'storage_tier_level': - iops_table.add_row([item.get('id'), item.get('description'), - item.get('keyName')]) - if item['itemCategory']['categoryCode'] == 'storage_snapshot_space': - snapshot_table.add_row([item.get('id'), item.get('description'), - item.get('keyName')]) +def _ios_get_table(items, prices): + if prices: + table = formatting.Table(['Id', 'Description', 'KeyName', 'Prices'], title='IOPS') + for item in items: + if item['itemCategory']['categoryCode'] == 'storage_tier_level': + table.add_row([item.get('id'), item.get('description'), + item.get('keyName'), item['prices'][0]['recurringFee']]) + else: + table = formatting.Table(['Id', 'Description', 'KeyName'], title='IOPS') + for item in items: + if item['itemCategory']['categoryCode'] == 'storage_tier_level': + table.add_row([item.get('id'), item.get('description'), + item.get('keyName')]) + table.sortby = 'Id' + table.align = 'l' + return table - env.fout(datacenter_table) - env.fout(iops_table) - env.fout(storage_table) - env.fout(snapshot_table) + +def _storage_table(items, prices): + if prices: + table = formatting.Table(['Id', 'Description', 'KeyName', 'Capacity Minimum', 'Prices'], title='Storage') + for item in items: + if item['itemCategory']['categoryCode'] == 'performance_storage_space': + table.add_row([item.get('id'), item.get('description'), + item.get('keyName'), item.get('capacityMinimum') or '-', + item['prices'][0]['recurringFee']]) + else: + table = formatting.Table(['Id', 'Description', 'KeyName', 'Capacity Minimum'], title='Storage') + for item in items: + if item['itemCategory']['categoryCode'] == 'performance_storage_space': + table.add_row([item.get('id'), item.get('description'), + item.get('keyName'), item.get('capacityMinimum') or '-', ]) + table.sortby = 'Id' + table.align = 'l' + return table + + +def _snapshot_get_table(items, prices): + if prices: + table = formatting.Table(['Id', 'Description', 'KeyName', 'Prices'], title='Snapshot') + for item in items: + if item['itemCategory']['categoryCode'] == 'storage_snapshot_space': + table.add_row([item.get('id'), item.get('description'), + item.get('keyName'), item['prices'][0]['recurringFee']]) + else: + table = formatting.Table(['Id', 'Description', 'KeyName'], title='Snapshot') + for item in items: + if item['itemCategory']['categoryCode'] == 'storage_snapshot_space': + table.add_row([item.get('id'), item.get('description'), + item.get('keyName')]) + table.sortby = 'Id' + table.align = 'l' + return table diff --git a/SoftLayer/CLI/file/options.py b/SoftLayer/CLI/file/options.py index dfaa3f1ed..ba916f7e0 100644 --- a/SoftLayer/CLI/file/options.py +++ b/SoftLayer/CLI/file/options.py @@ -12,41 +12,83 @@ @click.command(cls=SLCommand) +@click.argument('location', required=False) +@click.option('--prices', '-p', is_flag=True, + help='Use --prices to list the server item prices, and to list the Item Prices by location,' + 'add it to the --prices option using location short name, e.g. --prices dal13') @environment.pass_env -def cli(env): - """List all options for ordering a file storage""" +def cli(env, prices, location=None): + """List all options for ordering a block storage""" order_manager = SoftLayer.OrderingManager(env.client) items = order_manager.get_items(PACKAGE_STORAGE) - datacenters = order_manager.get_regions(PACKAGE_STORAGE) + datacenters = order_manager.get_regions(PACKAGE_STORAGE, location) - iops_table = formatting.Table(['Id', 'Description', 'KeyName'], title='IOPS') - snapshot_table = formatting.Table(['Id', 'Description', 'KeyName'], title='Snapshot') - file_storage_table = formatting.Table(['Id', 'Description', 'KeyName', 'Capacity Minimum'], title='Storage') + tables = [] datacenter_table = formatting.Table(['Id', 'Description', 'KeyName'], title='Datacenter') - file_storage_table.align['Description'] = 'l' - file_storage_table.align['KeyName'] = 'l' - file_storage_table.sortby = 'Id' for datacenter in datacenters: datacenter_table.add_row([datacenter['location']['locationId'], datacenter.get('description'), datacenter['keyname']]) - for item in items: - if item['itemCategory']['categoryCode'] == 'performance_storage_space': - file_storage_table.add_row([item.get('id'), item.get('description'), - item.get('keyName'), item.get('capacityMinimum') or '-']) + tables.append(datacenter_table) + tables.append(_ios_get_table(items, prices)) + tables.append(_file_storage_table(items, prices)) + tables.append(_snapshot_get_table(items, prices)) + env.fout(tables) - if item['itemCategory']['categoryCode'] == 'storage_tier_level': - iops_table.add_row([item.get('id'), item.get('description'), - item.get('keyName')]) - if item['itemCategory']['categoryCode'] == 'storage_snapshot_space': - snapshot_table.add_row([item.get('id'), item.get('description'), - item.get('keyName')]) +def _ios_get_table(items, prices): + if prices: + table = formatting.Table(['Id', 'Description', 'KeyName', 'Prices'], title='IOPS') + for item in items: + if item['itemCategory']['categoryCode'] == 'storage_tier_level': + table.add_row([item.get('id'), item.get('description'), + item.get('keyName'), item['prices'][0]['recurringFee']]) + else: + table = formatting.Table(['Id', 'Description', 'KeyName'], title='IOPS') + for item in items: + if item['itemCategory']['categoryCode'] == 'storage_tier_level': + table.add_row([item.get('id'), item.get('description'), + item.get('keyName')]) + table.sortby = 'Id' + table.align = 'l' + return table - env.fout(datacenter_table) - env.fout(iops_table) - env.fout(file_storage_table) - env.fout(snapshot_table) + +def _file_storage_table(items, prices): + if prices: + table = formatting.Table(['Id', 'Description', 'KeyName', 'Capacity Minimum', 'Prices'], title='Storage') + for item in items: + if item['itemCategory']['categoryCode'] == 'performance_storage_space': + table.add_row([item.get('id'), item.get('description'), + item.get('keyName'), item.get('capacityMinimum') or '-', + item['prices'][0]['recurringFee']]) + else: + table = formatting.Table(['Id', 'Description', 'KeyName', 'Capacity Minimum'], title='Storage') + for item in items: + if item['itemCategory']['categoryCode'] == 'performance_storage_space': + table.add_row([item.get('id'), item.get('description'), + item.get('keyName'), item.get('capacityMinimum') or '-', ]) + table.sortby = 'Id' + table.align = 'l' + return table + + +def _snapshot_get_table(items, prices): + if prices: + table = formatting.Table(['Id', 'Description', 'KeyName', 'Prices'], title='Snapshot') + for item in items: + if item['itemCategory']['categoryCode'] == 'storage_snapshot_space': + table.add_row([item.get('id'), item.get('description'), + item.get('keyName'), item['prices'][0]['recurringFee']]) + else: + table = formatting.Table(['Id', 'Description', 'KeyName'], title='Snapshot') + for item in items: + if item['itemCategory']['categoryCode'] == 'storage_snapshot_space': + table.add_row([item.get('id'), item.get('description'), + item.get('keyName')]) + table.sortby = 'Id' + table.align = 'l' + return table diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index 26bbed43d..bfa33731e 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -728,9 +728,12 @@ def get_items(self, package_id, storage_filter=None): return self.client.call('SoftLayer_Product_Package', 'getItems', filter=storage_filter, id=package_id) - def get_regions(self, package_id): + def get_regions(self, package_id, location=None): """returns the all regions. :param int package_id: The package for which to get the items. """ - return self.client.call('SoftLayer_Product_Package', 'getRegions', id=package_id) + _filter = '' + if location: + _filter = {"regions": {"location": {"location": {"name": {"operation": location}}}}} + return self.client.call('SoftLayer_Product_Package', 'getRegions', id=package_id, filter=_filter) From 91da7b3f835210a0d68c97e956a8beb98ac68d11 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 22 Jul 2022 17:43:31 -0400 Subject: [PATCH 050/710] fix the tox tool --- SoftLayer/CLI/file/options.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/file/options.py b/SoftLayer/CLI/file/options.py index ba916f7e0..ffcdfa35b 100644 --- a/SoftLayer/CLI/file/options.py +++ b/SoftLayer/CLI/file/options.py @@ -33,13 +33,13 @@ def cli(env, prices, location=None): datacenter['keyname']]) tables.append(datacenter_table) - tables.append(_ios_get_table(items, prices)) + tables.append(_file_ios_get_table(items, prices)) tables.append(_file_storage_table(items, prices)) - tables.append(_snapshot_get_table(items, prices)) + tables.append(_file_snapshot_get_table(items, prices)) env.fout(tables) -def _ios_get_table(items, prices): +def _file_ios_get_table(items, prices): if prices: table = formatting.Table(['Id', 'Description', 'KeyName', 'Prices'], title='IOPS') for item in items: @@ -76,7 +76,7 @@ def _file_storage_table(items, prices): return table -def _snapshot_get_table(items, prices): +def _file_snapshot_get_table(items, prices): if prices: table = formatting.Table(['Id', 'Description', 'KeyName', 'Prices'], title='Snapshot') for item in items: From 1f5e7d62fa31b871ff85152b7525d539cfa1885b Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 22 Jul 2022 17:57:35 -0400 Subject: [PATCH 051/710] fix the tox tool --- SoftLayer/CLI/block/options.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/SoftLayer/CLI/block/options.py b/SoftLayer/CLI/block/options.py index a3a73fdc6..d417c932b 100644 --- a/SoftLayer/CLI/block/options.py +++ b/SoftLayer/CLI/block/options.py @@ -33,13 +33,13 @@ def cli(env, prices, location=None): datacenter['keyname']]) tables.append(datacenter_table) - tables.append(_ios_get_table(items, prices)) - tables.append(_storage_table(items, prices)) - tables.append(_snapshot_get_table(items, prices)) + tables.append(_block_ios_get_table(items, prices)) + tables.append(_block_storage_table(items, prices)) + tables.append(_block_snapshot_get_table(items, prices)) env.fout(tables) -def _ios_get_table(items, prices): +def _block_ios_get_table(items, prices): if prices: table = formatting.Table(['Id', 'Description', 'KeyName', 'Prices'], title='IOPS') for item in items: @@ -57,7 +57,7 @@ def _ios_get_table(items, prices): return table -def _storage_table(items, prices): +def _block_storage_table(items, prices): if prices: table = formatting.Table(['Id', 'Description', 'KeyName', 'Capacity Minimum', 'Prices'], title='Storage') for item in items: @@ -76,7 +76,7 @@ def _storage_table(items, prices): return table -def _snapshot_get_table(items, prices): +def _block_snapshot_get_table(items, prices): if prices: table = formatting.Table(['Id', 'Description', 'KeyName', 'Prices'], title='Snapshot') for item in items: From f0b41d2565e7309528149edcbdb53e3374dab6a3 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 22 Jul 2022 18:04:23 -0400 Subject: [PATCH 052/710] fix the tox tool --- SoftLayer/CLI/block/options.py | 50 +++++++++++++++++----------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/SoftLayer/CLI/block/options.py b/SoftLayer/CLI/block/options.py index d417c932b..0018bead7 100644 --- a/SoftLayer/CLI/block/options.py +++ b/SoftLayer/CLI/block/options.py @@ -42,16 +42,16 @@ def cli(env, prices, location=None): def _block_ios_get_table(items, prices): if prices: table = formatting.Table(['Id', 'Description', 'KeyName', 'Prices'], title='IOPS') - for item in items: - if item['itemCategory']['categoryCode'] == 'storage_tier_level': - table.add_row([item.get('id'), item.get('description'), - item.get('keyName'), item['prices'][0]['recurringFee']]) + for block_item in items: + if block_item['itemCategory']['categoryCode'] == 'storage_tier_level': + table.add_row([block_item.get('id'), block_item.get('description'), + block_item.get('keyName'), block_item['prices'][0]['recurringFee']]) else: table = formatting.Table(['Id', 'Description', 'KeyName'], title='IOPS') - for item in items: - if item['itemCategory']['categoryCode'] == 'storage_tier_level': - table.add_row([item.get('id'), item.get('description'), - item.get('keyName')]) + for block_item in items: + if block_item['itemCategory']['categoryCode'] == 'storage_tier_level': + table.add_row([block_item.get('id'), block_item.get('description'), + block_item.get('keyName')]) table.sortby = 'Id' table.align = 'l' return table @@ -60,17 +60,17 @@ def _block_ios_get_table(items, prices): def _block_storage_table(items, prices): if prices: table = formatting.Table(['Id', 'Description', 'KeyName', 'Capacity Minimum', 'Prices'], title='Storage') - for item in items: - if item['itemCategory']['categoryCode'] == 'performance_storage_space': - table.add_row([item.get('id'), item.get('description'), - item.get('keyName'), item.get('capacityMinimum') or '-', - item['prices'][0]['recurringFee']]) + for block_item in items: + if block_item['itemCategory']['categoryCode'] == 'performance_storage_space': + table.add_row([block_item.get('id'), block_item.get('description'), + block_item.get('keyName'), block_item.get('capacityMinimum') or '-', + block_item['prices'][0]['recurringFee']]) else: table = formatting.Table(['Id', 'Description', 'KeyName', 'Capacity Minimum'], title='Storage') - for item in items: - if item['itemCategory']['categoryCode'] == 'performance_storage_space': - table.add_row([item.get('id'), item.get('description'), - item.get('keyName'), item.get('capacityMinimum') or '-', ]) + for block_item in items: + if block_item['itemCategory']['categoryCode'] == 'performance_storage_space': + table.add_row([block_item.get('id'), block_item.get('description'), + block_item.get('keyName'), block_item.get('capacityMinimum') or '-', ]) table.sortby = 'Id' table.align = 'l' return table @@ -79,16 +79,16 @@ def _block_storage_table(items, prices): def _block_snapshot_get_table(items, prices): if prices: table = formatting.Table(['Id', 'Description', 'KeyName', 'Prices'], title='Snapshot') - for item in items: - if item['itemCategory']['categoryCode'] == 'storage_snapshot_space': - table.add_row([item.get('id'), item.get('description'), - item.get('keyName'), item['prices'][0]['recurringFee']]) + for block_item in items: + if block_item['itemCategory']['categoryCode'] == 'storage_snapshot_space': + table.add_row([block_item.get('id'), block_item.get('description'), + block_item.get('keyName'), block_item['prices'][0]['recurringFee']]) else: table = formatting.Table(['Id', 'Description', 'KeyName'], title='Snapshot') - for item in items: - if item['itemCategory']['categoryCode'] == 'storage_snapshot_space': - table.add_row([item.get('id'), item.get('description'), - item.get('keyName')]) + for block_item in items: + if block_item['itemCategory']['categoryCode'] == 'storage_snapshot_space': + table.add_row([block_item.get('id'), block_item.get('description'), + block_item.get('keyName')]) table.sortby = 'Id' table.align = 'l' return table From 8ada5e6e7503054e9e7995369a1ea0f7aaa4f7f9 Mon Sep 17 00:00:00 2001 From: edsonarios Date: Mon, 25 Jul 2022 12:38:47 -0400 Subject: [PATCH 053/710] command slcli vlan create, fixed, now is required datacenter or pod --- SoftLayer/CLI/vlan/create.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/vlan/create.py b/SoftLayer/CLI/vlan/create.py index a6a9128ef..93f89a780 100644 --- a/SoftLayer/CLI/vlan/create.py +++ b/SoftLayer/CLI/vlan/create.py @@ -19,7 +19,16 @@ help="Billing rate") @environment.pass_env def cli(env, name, datacenter, pod, network, billing): - """Order/create a VLAN instance.""" + """Order/create a VLAN instance. + + Example: + slcli vlan create --name myvlan --datacenter dal13 + or + slcli vlan create --name myvlan --pod dal10.pod01 + """ + + if not (datacenter or pod): + raise exceptions.CLIAbort("Is required Datacenter or Pod") item_package = ['PUBLIC_NETWORK_VLAN'] complex_type = 'SoftLayer_Container_Product_Order_Network_Vlan' From dec28cbb3b0bee5860a212e3210d3638d350f5e0 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 25 Jul 2022 17:02:09 -0400 Subject: [PATCH 054/710] fix the team code review commens --- SoftLayer/CLI/vpn/ipsec/order.py | 5 +++++ tests/managers/ipsec_tests.py | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/SoftLayer/CLI/vpn/ipsec/order.py b/SoftLayer/CLI/vpn/ipsec/order.py index 9681c2809..2ca6413ef 100644 --- a/SoftLayer/CLI/vpn/ipsec/order.py +++ b/SoftLayer/CLI/vpn/ipsec/order.py @@ -6,6 +6,7 @@ import SoftLayer from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting @@ -17,6 +18,10 @@ def cli(env, datacenter): ipsec_manager = SoftLayer.IPSECManager(env.client) + if not (env.skip_confirmations or formatting.confirm( + "This action will incur charges on your account. Continue?")): + raise exceptions.CLIAbort('Aborting ipsec order.') + result = ipsec_manager.order(datacenter, ['IPSEC_STANDARD']) table = formatting.KeyValueTable(['Name', 'Value']) diff --git a/tests/managers/ipsec_tests.py b/tests/managers/ipsec_tests.py index f88e33ed5..1d86f20d8 100644 --- a/tests/managers/ipsec_tests.py +++ b/tests/managers/ipsec_tests.py @@ -8,6 +8,9 @@ import SoftLayer from SoftLayer.exceptions import SoftLayerAPIError +from SoftLayer.fixtures import SoftLayer_Product_Order +from SoftLayer.fixtures import SoftLayer_Product_Package + from SoftLayer import testing @@ -299,3 +302,20 @@ def test_update_tunnel_context(self): 'phaseTwoKeylife': 240, 'phaseTwoPerfectForwardSecrecy': 1},), identifier=445) + + def test_order(self): + _mock = self.set_mock('SoftLayer_Product_Package', 'getItems') + _mock.return_value = SoftLayer_Product_Package.getItems_IPSEC + + _mock = self.set_mock('SoftLayer_Product_Order', 'placeOrder') + _mock.return_value = SoftLayer_Product_Order.ipsec_placeOrder + result = self.ipsec.order('dal13', ['IPSEC_STANDARD']) + order = { + 'orderDate': '2022-07-14T16:09:08-06:00', + 'orderId': 123456, 'placedOrder': {'items': [ + {'categoryCode': 'network_tunnel', + 'description': 'IPSEC - Standard', + 'id': 931479898, + 'itemId': 1092, + 'itemPriceId': '2048'}]}} + self.assertEqual(result, order) From e89e151e0031caac71c2adcfef4dd24af7f2f1a0 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 25 Jul 2022 19:00:26 -0400 Subject: [PATCH 055/710] fix the team code review comments --- SoftLayer/CLI/block/options.py | 42 +++++++++++++++++----------------- SoftLayer/CLI/file/options.py | 39 ++++++++++++++++--------------- 2 files changed, 41 insertions(+), 40 deletions(-) diff --git a/SoftLayer/CLI/block/options.py b/SoftLayer/CLI/block/options.py index 7833b0b5e..651e365bf 100644 --- a/SoftLayer/CLI/block/options.py +++ b/SoftLayer/CLI/block/options.py @@ -6,6 +6,7 @@ import SoftLayer from SoftLayer.CLI.command import SLCommand from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting PACKAGE_STORAGE = 759 @@ -24,30 +25,29 @@ def cli(env, prices, location=None): items = order_manager.get_items(PACKAGE_STORAGE) datacenters = order_manager.get_regions(PACKAGE_STORAGE, location) + tables = [] network = SoftLayer.NetworkManager(env.client) - pods = network.get_closed_pods() - iops_table = formatting.Table(['Id', 'Description', 'KeyName'], title='IOPS') - snapshot_table = formatting.Table(['Id', 'Description', 'KeyName'], title='Snapshot') - storage_table = formatting.Table(['Id', 'Description', 'KeyName'], title='Storage') - tables = [] - datacenter_table = formatting.Table(['Id', 'Description', 'KeyName'], title='Datacenter') - - for datacenter in datacenters: - closure = [] - for pod in pods: - if datacenter['location']['location']['name'] in str(pod['name']): - closure.append(pod['name']) - - notes = '-' - if len(closure) > 0: - notes = 'closed soon: %s' % (', '.join(closure)) - datacenter_table.add_row([datacenter['location']['locationId'], - datacenter.get('description'), - datacenter['keyname'], notes]) - - tables.append(datacenter_table) + if datacenters != []: + datacenter_table = formatting.Table(['Id', 'Description', 'KeyName'], title='Datacenter') + + for datacenter in datacenters: + closure = [] + for pod in pods: + if datacenter['location']['location']['name'] in str(pod['name']): + closure.append(pod['name']) + + notes = '-' + if len(closure) > 0: + notes = 'closed soon: %s' % (', '.join(closure)) + datacenter_table.add_row([datacenter['location']['locationId'], + datacenter.get('description'), + datacenter['keyname'], notes]) + tables.append(datacenter_table) + else: + raise exceptions.CLIAbort('Location does not exit.') + tables.append(_block_ios_get_table(items, prices)) tables.append(_block_storage_table(items, prices)) tables.append(_block_snapshot_get_table(items, prices)) diff --git a/SoftLayer/CLI/file/options.py b/SoftLayer/CLI/file/options.py index 42782f290..d66b526af 100644 --- a/SoftLayer/CLI/file/options.py +++ b/SoftLayer/CLI/file/options.py @@ -6,6 +6,7 @@ import SoftLayer from SoftLayer.CLI.command import SLCommand from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting PACKAGE_STORAGE = 759 @@ -29,25 +30,25 @@ def cli(env, prices, location=None): pods = network.get_closed_pods() - iops_table = formatting.Table(['Id', 'Description', 'KeyName'], title='IOPS') - snapshot_table = formatting.Table(['Id', 'Description', 'KeyName'], title='Snapshot') - file_storage_table = formatting.Table(['Id', 'Description', 'KeyName'], title='Storage') - datacenter_table = formatting.Table(['Id', 'Description', 'KeyName'], title='Datacenter') - - for datacenter in datacenters: - closure = [] - for pod in pods: - if datacenter['location']['location']['name'] in str(pod['name']): - closure.append(pod['name']) - - notes = '-' - if len(closure) > 0: - notes = 'closed soon: %s' % (', '.join(closure)) - datacenter_table.add_row([datacenter['location']['locationId'], - datacenter.get('description'), - datacenter['keyname'], notes]) - - tables.append(datacenter_table) + if datacenters != []: + datacenter_table = formatting.Table(['Id', 'Description', 'KeyName'], title='Datacenter') + + for datacenter in datacenters: + closure = [] + for pod in pods: + if datacenter['location']['location']['name'] in str(pod['name']): + closure.append(pod['name']) + + notes = '-' + if len(closure) > 0: + notes = 'closed soon: %s' % (', '.join(closure)) + datacenter_table.add_row([datacenter['location']['locationId'], + datacenter.get('description'), + datacenter['keyname'], notes]) + tables.append(datacenter_table) + else: + raise exceptions.CLIAbort('Location does not exit.') + tables.append(_file_ios_get_table(items, prices)) tables.append(_file_storage_table(items, prices)) tables.append(_file_snapshot_get_table(items, prices)) From 4166544fb917101833539039bfc07c65b3518be6 Mon Sep 17 00:00:00 2001 From: edsonarios Date: Tue, 26 Jul 2022 11:07:28 -0400 Subject: [PATCH 056/710] Bug fixed in command slcli vlan edit - Now at least one flag is required --- SoftLayer/CLI/vlan/edit.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SoftLayer/CLI/vlan/edit.py b/SoftLayer/CLI/vlan/edit.py index ff7c426dd..f41a6cd1d 100644 --- a/SoftLayer/CLI/vlan/edit.py +++ b/SoftLayer/CLI/vlan/edit.py @@ -5,6 +5,7 @@ import SoftLayer from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions from SoftLayer.CLI import helpers @@ -22,6 +23,9 @@ def cli(env, identifier, name, note, tags): """Edit a vlan's details.""" + if not (name or note or tags): + raise exceptions.CLIAbort("Is required Name or Note or Tags") + new_tags = None if tags: From 4f187859d010cb420b49a545371a6121b5fe1551 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 28 Jul 2022 10:54:52 -0400 Subject: [PATCH 057/710] add the new command on user. --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/user/device_access.py | 60 +++++++++++++++ SoftLayer/fixtures/SoftLayer_User_Customer.py | 76 +++++++++++++++++++ SoftLayer/managers/user.py | 24 ++++++ docs/cli/users.rst | 4 + tests/CLI/modules/user_tests.py | 8 ++ tests/managers/user_tests.py | 12 +++ 7 files changed, 185 insertions(+) create mode 100644 SoftLayer/CLI/user/device_access.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 892d2081e..2aa793d40 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -362,6 +362,7 @@ ('user:edit-details', 'SoftLayer.CLI.user.edit_details:cli'), ('user:create', 'SoftLayer.CLI.user.create:cli'), ('user:delete', 'SoftLayer.CLI.user.delete:cli'), + ('user:device-access', 'SoftLayer.CLI.user.device_access:cli'), ('user:vpn-manual', 'SoftLayer.CLI.user.vpn_manual:cli'), ('user:vpn-subnet', 'SoftLayer.CLI.user.vpn_subnet:cli'), diff --git a/SoftLayer/CLI/user/device_access.py b/SoftLayer/CLI/user/device_access.py new file mode 100644 index 000000000..a6c8c782e --- /dev/null +++ b/SoftLayer/CLI/user/device_access.py @@ -0,0 +1,60 @@ +"""List User Device access.""" +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +@click.command(cls=SoftLayer.CLI.command.SLCommand, ) +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """User Device access.""" + + mgr = SoftLayer.UserManager(env.client) + all_permissions = mgr.get_user_permissions(identifier) + + # verify the table in table + table = formatting.Table(['Name', 'Value']) + permission_table = formatting.Table(['KeyName', 'Name']) + for permission in all_permissions: + if 'ALL_' in permission['key']: + permission_table.add_row([permission.get('keyName'), permission.get('name')]) + + hardwares = mgr.get_user_hardware(identifier) + dedicatedhosts = mgr.get_user_dedicated_host(identifier) + virtualGuests = mgr.get_user_virtuals(identifier) + hardware_table = formatting.KeyValueTable(['Id', 'Device Name', 'Device type', 'Public Ip', 'Private Ip', 'notes']) + virtual_table = formatting.KeyValueTable(['Id', 'Device Name', 'Device type', 'Public Ip', 'Private Ip', 'notes']) + dedicated_table = formatting.KeyValueTable(['Id', 'Device Name', 'Device type', 'notes']) + + hardware_table.align['Device Name'] = 'l' + dedicated_table.align['Device Name'] = 'l' + virtual_table.align['Device Name'] = 'l' + for hw in hardwares: + hardware_table.add_row([hw.get('id'), + hw.get('fullyQualifiedDomainName'), + 'Bare Metal', + hw.get('primaryIpAddress'), + hw.get('primaryBackendIpAddress'), + hw.get('notes') or '-']) + for host in dedicatedhosts: + dedicated_table.add_row([host.get('id'), + host.get('name'), + 'Dedicated Host', + host.get('notes') or '-']) + for vs in virtualGuests: + virtual_table.add_row([vs.get('id'), + vs.get('fullyQualifiedDomainName'), + 'virtual Guests', + vs.get('primaryIpAddress'), + vs.get('primaryBackendIpAddress'), + vs.get('notes') or '-']) + + table.add_row(['Permission', permission_table]) + table.add_row(['Hardware', hardware_table]) + table.add_row(['Dedicated Host', dedicated_table]) + table.add_row(['Virtual Guest', virtual_table]) + + env.fout(table) diff --git a/SoftLayer/fixtures/SoftLayer_User_Customer.py b/SoftLayer/fixtures/SoftLayer_User_Customer.py index 42c8f84cc..789c22f9a 100644 --- a/SoftLayer/fixtures/SoftLayer_User_Customer.py +++ b/SoftLayer/fixtures/SoftLayer_User_Customer.py @@ -86,3 +86,79 @@ editObject = True addApiAuthenticationKey = True updateVpnUser = True + +getHardware = [{ + "domain": "testedit.com", + "fullyQualifiedDomainName": "test.testedit.com", + "hardwareStatusId": 5, + "hostname": "test", + "id": 1403539, + "manufacturerSerialNumber": "J33H9TX", + "notes": "My golang note", + "provisionDate": "2020-04-27T16:10:56-06:00", + "serialNumber": "SL01FJUI", + "globalIdentifier": "81434794-af69-44d5-bb97-6b6f43454eee", + "hardwareStatus": { + "id": 5, + "status": "ACTIVE" + }, + "networkManagementIpAddress": "10.93.138.222", + "primaryBackendIpAddress": "10.93.138.202", + "primaryIpAddress": "169.48.191.244", + "privateIpAddress": "10.93.138.202" + }] +getDedicatedHosts = [ + { + 'createDate': '2021-11-18T15:13:57-06:00', + 'diskCapacity': 1200, + 'id': 656700, + 'memoryCapacity': 242, + 'modifyDate': '2022-04-26T10:49:48-06:00', + 'name': 'dedicatedhost01' + }, + { + 'accountId': 307608, + 'cpuCount': 56, + 'createDate': '2022-02-18T12:47:30-06:00', + 'diskCapacity': 1200, + 'id': 691394, + 'memoryCapacity': 242, + 'modifyDate': '2022-04-18T11:24:20-06:00', + 'name': 'test' + } +] +getVirtualGuests = [ + { + "fullyQualifiedDomainName": "KVM-Test.cgallo.com", + "hostname": "KVM-Test", + "id": 121401696, + "maxCpu": 2, + "maxCpuUnits": "CORE", + "maxMemory": 4096, + "modifyDate": "2022-01-25T23:23:13-06:00", + "provisionDate": "2021-06-09T14:51:54-06:00", + "startCpus": 2, + "typeId": 1, + "uuid": "15951561-6171-0dfc-f3d2-be039e51cc10", + "globalIdentifier": "a245a7dd-acd1-4d1a-9356-cc1ac6b55b98", + "primaryBackendIpAddress": "10.208.73.53", + "primaryIpAddress": "169.48.96.27", + }, + { + "createDate": "2020-11-16T09:01:57-06:00", + "deviceStatusId": 8, + "domain": "softlayer.test", + "fullyQualifiedDomainName": "SuspendVsTest.softlayer.test", + "hostname": "SuspendVsTest", + "id": 112238162, + "maxCpu": 8, + "maxCpuUnits": "CORE", + "maxMemory": 16384, + "modifyDate": "2022-01-25T14:15:37-06:00", + "provisionDate": "2020-11-16T09:09:04-06:00", + "startCpus": 8, + "typeId": 1, + "uuid": "d8908a64-f4d4-5637-49c7-650572d47120", + "globalIdentifier": "7fe777af-d38b-47c2-9f1c-b1ec26751b58", + "primaryBackendIpAddress": "10.74.54.76", + }] diff --git a/SoftLayer/managers/user.py b/SoftLayer/managers/user.py index 56acf163e..b49998444 100644 --- a/SoftLayer/managers/user.py +++ b/SoftLayer/managers/user.py @@ -392,6 +392,30 @@ def get_overrides_list(self, user_id, subnet_ids): return overrides_list + def get_user_hardware(self, user_id): + """User Hardware list. + + :param int user_id: + :return: List hardware relate to user + """ + return self.user_service.getHardware(id=user_id) + + def get_user_dedicated_host(self, user_id): + """User dedicate host list. + + :param int user_id: + :return: List dedicated host relate to user + """ + return self.user_service.getDedicatedHosts(id=user_id) + + def get_user_virtuals(self, user_id): + """User virtual guest list. + + :param int user_id: + :return: List virtual guest relate to user + """ + return self.user_service.getVirtualGuests(id=user_id) + def _keyname_search(haystack, needle): for item in haystack: diff --git a/docs/cli/users.rst b/docs/cli/users.rst index 5195a7788..e4ab6046d 100644 --- a/docs/cli/users.rst +++ b/docs/cli/users.rst @@ -40,6 +40,10 @@ Version 5.6.0 introduces the ability to interact with user accounts from the cli :prog: user delete :show-nested: +.. click:: SoftLayer.CLI.user.device_access:cli + :prog: user device-access + :show-nested: + .. click:: SoftLayer.CLI.user.vpn_manual:cli :prog: user vpn-manual :show-nested: diff --git a/tests/CLI/modules/user_tests.py b/tests/CLI/modules/user_tests.py index a11a94838..acb35ba24 100644 --- a/tests/CLI/modules/user_tests.py +++ b/tests/CLI/modules/user_tests.py @@ -336,3 +336,11 @@ def test_edit_notification_off_failure(self, click): click.secho.assert_called_with('Failed to update notifications: Test notification', fg='red') self.assert_no_fail(result) self.assert_called_with('SoftLayer_Email_Subscription', 'disable', identifier=111) + + def test_devices_access(self): + result = self.run_command(['user', 'device-access', '111']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_User_Customer', 'getPermissions') + self.assert_called_with('SoftLayer_User_Customer', 'getHardware') + self.assert_called_with('SoftLayer_User_Customer', 'getDedicatedHosts') + self.assert_called_with('SoftLayer_User_Customer', 'getVirtualGuests') diff --git a/tests/managers/user_tests.py b/tests/managers/user_tests.py index 5ea4d2696..a0a201551 100644 --- a/tests/managers/user_tests.py +++ b/tests/managers/user_tests.py @@ -294,3 +294,15 @@ def test_gather_notifications_fail(self): self.manager.gather_notifications, ['Test not exit']) self.assertEqual("Test not exit is not a valid notification name", str(ex)) + + def test_get_hardware(self): + self.manager.get_user_hardware(1234) + self.assert_called_with('SoftLayer_User_Customer', 'getHardware') + + def test_get_dedicated_host(self): + self.manager.get_user_dedicated_host(1234) + self.assert_called_with('SoftLayer_User_Customer', 'getDedicatedHosts') + + def test_get_virtual(self): + self.manager.get_user_virtuals(1234) + self.assert_called_with('SoftLayer_User_Customer', 'getVirtualGuests') From e4cc32aacb0945f540a01b6bd323be4140fb114f Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 28 Jul 2022 11:35:17 -0400 Subject: [PATCH 058/710] fix the tox tool --- SoftLayer/CLI/user/device_access.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/SoftLayer/CLI/user/device_access.py b/SoftLayer/CLI/user/device_access.py index a6c8c782e..4ad0f2951 100644 --- a/SoftLayer/CLI/user/device_access.py +++ b/SoftLayer/CLI/user/device_access.py @@ -24,7 +24,7 @@ def cli(env, identifier): hardwares = mgr.get_user_hardware(identifier) dedicatedhosts = mgr.get_user_dedicated_host(identifier) - virtualGuests = mgr.get_user_virtuals(identifier) + virtual_guests = mgr.get_user_virtuals(identifier) hardware_table = formatting.KeyValueTable(['Id', 'Device Name', 'Device type', 'Public Ip', 'Private Ip', 'notes']) virtual_table = formatting.KeyValueTable(['Id', 'Device Name', 'Device type', 'Public Ip', 'Private Ip', 'notes']) dedicated_table = formatting.KeyValueTable(['Id', 'Device Name', 'Device type', 'notes']) @@ -32,25 +32,25 @@ def cli(env, identifier): hardware_table.align['Device Name'] = 'l' dedicated_table.align['Device Name'] = 'l' virtual_table.align['Device Name'] = 'l' - for hw in hardwares: - hardware_table.add_row([hw.get('id'), - hw.get('fullyQualifiedDomainName'), + for hardware in hardwares: + hardware_table.add_row([hardware.get('id'), + hardware.get('fullyQualifiedDomainName'), 'Bare Metal', - hw.get('primaryIpAddress'), - hw.get('primaryBackendIpAddress'), - hw.get('notes') or '-']) + hardware.get('primaryIpAddress'), + hardware.get('primaryBackendIpAddress'), + hardware.get('notes') or '-']) for host in dedicatedhosts: dedicated_table.add_row([host.get('id'), host.get('name'), 'Dedicated Host', host.get('notes') or '-']) - for vs in virtualGuests: - virtual_table.add_row([vs.get('id'), - vs.get('fullyQualifiedDomainName'), + for virtual in virtual_guests: + virtual_table.add_row([virtual.get('id'), + virtual.get('fullyQualifiedDomainName'), 'virtual Guests', - vs.get('primaryIpAddress'), - vs.get('primaryBackendIpAddress'), - vs.get('notes') or '-']) + virtual.get('primaryIpAddress'), + virtual.get('primaryBackendIpAddress'), + virtual.get('notes') or '-']) table.add_row(['Permission', permission_table]) table.add_row(['Hardware', hardware_table]) From 27ed7fa62e0ee882a86c7b2926d2c56587d4a0ba Mon Sep 17 00:00:00 2001 From: edsonarios Date: Thu, 28 Jul 2022 11:12:36 -0400 Subject: [PATCH 059/710] Updated command - slcli vlan list --- SoftLayer/CLI/vlan/list.py | 47 +++++++++++++++++++++++++++-------- SoftLayer/managers/network.py | 16 ++++++++++++ 2 files changed, 52 insertions(+), 11 deletions(-) diff --git a/SoftLayer/CLI/vlan/list.py b/SoftLayer/CLI/vlan/list.py index c8c9dc81c..6285db75a 100644 --- a/SoftLayer/CLI/vlan/list.py +++ b/SoftLayer/CLI/vlan/list.py @@ -9,16 +9,19 @@ from SoftLayer.CLI.vlan.detail import get_gateway_firewall from SoftLayer import utils -COLUMNS = ['id', - 'number', - 'name', +COLUMNS = ['Id', + 'Number', + 'Fully qualified name', + 'Name', + 'Network', + 'Data center', + 'Pod', 'Gateway/Firewall', - 'datacenter', - 'hardware', - 'virtual_servers', - 'public_ips', - 'premium', - 'tag'] + 'Hardware', + 'Virtual servers', + 'Public ips', + 'Premium', + 'Tags'] @click.command(cls=SoftLayer.CLI.command.SLCommand, ) @@ -35,7 +38,10 @@ show_default=True) @environment.pass_env def cli(env, sortby, datacenter, number, name, limit): - """List VLANs.""" + """List VLANs. + + Note: In field Pod, if add (*) indicated that closed soon + """ mgr = SoftLayer.NetworkManager(env.client) @@ -46,15 +52,21 @@ def cli(env, sortby, datacenter, number, name, limit): vlan_number=number, name=name, limit=limit) + + pods = mgr.get_pods_with_capabilities() + for vlan in vlans: billing = 'Yes' if vlan.get('billingItem') else 'No' table.add_row([ vlan.get('id'), vlan.get('vlanNumber'), + vlan.get('fullyQualifiedName'), vlan.get('name') or formatting.blank(), - get_gateway_firewall(vlan), + vlan.get('networkSpace').capitalize(), utils.lookup(vlan, 'primaryRouter', 'datacenter', 'name'), + get_pod_with_closed_announcement(vlan, pods), + get_gateway_firewall(vlan), vlan.get('hardwareCount'), vlan.get('virtualGuestCount'), vlan.get('totalPrimaryIpAddressCount'), @@ -63,3 +75,16 @@ def cli(env, sortby, datacenter, number, name, limit): ]) env.fout(table) + + +def get_pod_with_closed_announcement(vlan, pods): + """Gets pods with announcement to close""" + for pod in pods: + if utils.lookup(pod, 'backendRouterId') == utils.lookup(vlan, 'primaryRouter', 'id') \ + or utils.lookup(pod, 'frontendRouterId') == utils.lookup(vlan, 'primaryRouter', 'id'): + if 'CLOSURE_ANNOUNCED' in utils.lookup(pod, 'capabilities'): + name_pod = utils.lookup(pod, 'name').split('.')[1] + '*' + return name_pod.capitalize() + else: + return utils.lookup(pod, 'name').split('.')[1].capitalize() + return '' diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index ae4cc8298..9ed7fc47d 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -866,3 +866,19 @@ def get_datacenter_by_keyname(self, keyname=None): if len(result) >= 1: return result[0] return {} + + def get_pods_with_capabilities(self): + """Calls SoftLayer_Network_Pod::getAllObjects() + + returns list of all pods with capabilities for see closing network pods. + """ + order_filter = { + 'name': { + 'operation': 'orderBy', + 'options': [{'name': 'sort', 'value': ['DESC']}] + } + } + + mask = """mask[name, datacenterLongName, frontendRouterId, capabilities, datacenterId, backendRouterId, + backendRouterName, frontendRouterName]""" + return self.client.call('SoftLayer_Network_Pod', 'getAllObjects', mask=mask, filter=order_filter) From 8c3edf1f7b66dda3c0f650621ba512ba96ddf711 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 29 Jul 2022 15:48:53 -0500 Subject: [PATCH 060/710] Update SoftLayer/CLI/vlan/create.py --- SoftLayer/CLI/vlan/create.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/vlan/create.py b/SoftLayer/CLI/vlan/create.py index 93f89a780..208cc272f 100644 --- a/SoftLayer/CLI/vlan/create.py +++ b/SoftLayer/CLI/vlan/create.py @@ -28,7 +28,7 @@ def cli(env, name, datacenter, pod, network, billing): """ if not (datacenter or pod): - raise exceptions.CLIAbort("Is required Datacenter or Pod") + raise exceptions.CLIAbort("--datacenter or --pod is required to create a VLAN") item_package = ['PUBLIC_NETWORK_VLAN'] complex_type = 'SoftLayer_Container_Product_Order_Network_Vlan' From 88099190521116845c6cf724cfbe50ce432b4e26 Mon Sep 17 00:00:00 2001 From: Brian Flores Date: Mon, 1 Aug 2022 11:18:45 -0400 Subject: [PATCH 061/710] remove create date column --- SoftLayer/CLI/block/subnets/list.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/SoftLayer/CLI/block/subnets/list.py b/SoftLayer/CLI/block/subnets/list.py index c0d2637c3..afc19c737 100644 --- a/SoftLayer/CLI/block/subnets/list.py +++ b/SoftLayer/CLI/block/subnets/list.py @@ -10,7 +10,6 @@ COLUMNS = [ 'id', - 'createDate', 'networkIdentifier', 'cidr' ] @@ -33,7 +32,6 @@ def cli(env, access_id): table = formatting.Table(COLUMNS) for subnet in subnets: row = ["{0}".format(subnet['id']), - "{0}".format(subnet['createDate']), "{0}".format(subnet['networkIdentifier']), "{0}".format(subnet['cidr'])] table.add_row(row) From 662b060566682456b4e0f58d8c5fcf80d75a8e1a Mon Sep 17 00:00:00 2001 From: edsonarios Date: Mon, 1 Aug 2022 13:40:55 -0400 Subject: [PATCH 062/710] Solved comments --- SoftLayer/CLI/vlan/list.py | 8 +++++--- SoftLayer/managers/network.py | 32 ++++++++++++-------------------- 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/SoftLayer/CLI/vlan/list.py b/SoftLayer/CLI/vlan/list.py index 6285db75a..fc5d4ca69 100644 --- a/SoftLayer/CLI/vlan/list.py +++ b/SoftLayer/CLI/vlan/list.py @@ -40,7 +40,7 @@ def cli(env, sortby, datacenter, number, name, limit): """List VLANs. - Note: In field Pod, if add (*) indicated that closed soon + Note: A * Indicates a POD is closing soon. Ex:[red] Pod01* [/red] """ mgr = SoftLayer.NetworkManager(env.client) @@ -53,7 +53,9 @@ def cli(env, sortby, datacenter, number, name, limit): name=name, limit=limit) - pods = mgr.get_pods_with_capabilities() + mask = """mask[name, datacenterLongName, frontendRouterId, capabilities, datacenterId, backendRouterId, + backendRouterName, frontendRouterName]""" + pods = mgr.get_pods(mask=mask) for vlan in vlans: billing = 'Yes' if vlan.get('billingItem') else 'No' @@ -84,7 +86,7 @@ def get_pod_with_closed_announcement(vlan, pods): or utils.lookup(pod, 'frontendRouterId') == utils.lookup(vlan, 'primaryRouter', 'id'): if 'CLOSURE_ANNOUNCED' in utils.lookup(pod, 'capabilities'): name_pod = utils.lookup(pod, 'name').split('.')[1] + '*' - return name_pod.capitalize() + return "[red]" + name_pod.capitalize() + "[/red]" else: return utils.lookup(pod, 'name').split('.')[1].capitalize() return '' diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 9ed7fc47d..f454fe4d9 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -786,17 +786,25 @@ def cancel_item(self, identifier, cancel_immediately, customer_note, id=identifier) - def get_pods(self, datacenter=None): + def get_pods(self, datacenter=None, mask=None): """Calls SoftLayer_Network_Pod::getAllObjects() returns list of all network pods and their routers. """ - _filter = None - + _filter = { + 'name': { + 'operation': 'orderBy', + 'options': [{'name': 'sort', 'value': ['DESC']}] + } + } if datacenter: _filter = {"datacenterName": {"operation": datacenter}} - return self.client.call('SoftLayer_Network_Pod', 'getAllObjects', filter=_filter) + _mask = None + if mask: + _mask = mask + + return self.client.call('SoftLayer_Network_Pod', 'getAllObjects', mask=_mask, filter=_filter) def get_list_datacenter(self): """Calls SoftLayer_Location::getDatacenters() @@ -866,19 +874,3 @@ def get_datacenter_by_keyname(self, keyname=None): if len(result) >= 1: return result[0] return {} - - def get_pods_with_capabilities(self): - """Calls SoftLayer_Network_Pod::getAllObjects() - - returns list of all pods with capabilities for see closing network pods. - """ - order_filter = { - 'name': { - 'operation': 'orderBy', - 'options': [{'name': 'sort', 'value': ['DESC']}] - } - } - - mask = """mask[name, datacenterLongName, frontendRouterId, capabilities, datacenterId, backendRouterId, - backendRouterName, frontendRouterName]""" - return self.client.call('SoftLayer_Network_Pod', 'getAllObjects', mask=mask, filter=order_filter) From c5cedc1afe37dc233938719eefe144635e84d555 Mon Sep 17 00:00:00 2001 From: edsonarios Date: Mon, 1 Aug 2022 13:48:08 -0400 Subject: [PATCH 063/710] Solved comments --- SoftLayer/CLI/vlan/edit.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/vlan/edit.py b/SoftLayer/CLI/vlan/edit.py index f41a6cd1d..e23126465 100644 --- a/SoftLayer/CLI/vlan/edit.py +++ b/SoftLayer/CLI/vlan/edit.py @@ -23,8 +23,8 @@ def cli(env, identifier, name, note, tags): """Edit a vlan's details.""" - if not (name or note or tags): - raise exceptions.CLIAbort("Is required Name or Note or Tags") + if not any([name, note, tags]): + raise exceptions.CLIAbort("At least one option is required") new_tags = None From 24771a79cf36831dd688d4303d1b6fe0920a5f15 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 1 Aug 2022 17:26:46 -0400 Subject: [PATCH 064/710] add user remove-access command --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/user/remove_access.py | 38 +++++++++++++++++++ SoftLayer/fixtures/SoftLayer_User_Customer.py | 3 ++ SoftLayer/managers/user.py | 30 +++++++++++++++ docs/cli/users.rst | 4 ++ tests/CLI/modules/user_tests.py | 12 ++++++ tests/managers/user_tests.py | 12 ++++++ 7 files changed, 100 insertions(+) create mode 100644 SoftLayer/CLI/user/remove_access.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 892d2081e..c579730f8 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -364,6 +364,7 @@ ('user:delete', 'SoftLayer.CLI.user.delete:cli'), ('user:vpn-manual', 'SoftLayer.CLI.user.vpn_manual:cli'), ('user:vpn-subnet', 'SoftLayer.CLI.user.vpn_subnet:cli'), + ('user:remove-access', 'SoftLayer.CLI.user.remove_access:cli'), ('vlan', 'SoftLayer.CLI.vlan'), ('vlan:create', 'SoftLayer.CLI.vlan.create:cli'), diff --git a/SoftLayer/CLI/user/remove_access.py b/SoftLayer/CLI/user/remove_access.py new file mode 100644 index 000000000..24cfa67bb --- /dev/null +++ b/SoftLayer/CLI/user/remove_access.py @@ -0,0 +1,38 @@ +"""User remove access to devices.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment + + +@click.command(cls=SoftLayer.CLI.command.SLCommand, ) +@click.argument('identifier') +@click.option('--hardware', '-h', + help="Display hardware this user has access to.") +@click.option('--virtual', '-v', + help="Display virtual guests this user has access to.") +@click.option('--dedicated', '-l', + help="dedicated host ID ") +@environment.pass_env +def cli(env, identifier, hardware, virtual, dedicated): + """User details.""" + + mgr = SoftLayer.UserManager(env.client) + device = '' + result = False + if hardware: + device = hardware + result = mgr.remove_hardware_access(identifier, hardware) + + if virtual: + device = virtual + result = mgr.remove_virtual_access(identifier, virtual) + + if dedicated: + device = dedicated + result = mgr.remove_dedicated_access(identifier, dedicated) + + if result: + click.secho("Remove to access to device: %s" % device, fg='green') diff --git a/SoftLayer/fixtures/SoftLayer_User_Customer.py b/SoftLayer/fixtures/SoftLayer_User_Customer.py index 42c8f84cc..f9f3a3f32 100644 --- a/SoftLayer/fixtures/SoftLayer_User_Customer.py +++ b/SoftLayer/fixtures/SoftLayer_User_Customer.py @@ -86,3 +86,6 @@ editObject = True addApiAuthenticationKey = True updateVpnUser = True +removeDedicatedHostAccess = True +removeHardwareAccess = True +removeVirtualGuestAccess = True diff --git a/SoftLayer/managers/user.py b/SoftLayer/managers/user.py index 56acf163e..8205e9610 100644 --- a/SoftLayer/managers/user.py +++ b/SoftLayer/managers/user.py @@ -392,6 +392,36 @@ def get_overrides_list(self, user_id, subnet_ids): return overrides_list + def remove_hardware_access(self, user_id, hardware_id): + """Remove hardware from a portal user’s hardware access list. + + :param int user_id: + :param int hardware_id + + :returns: true + """ + return self.user_service.removeHardwareAccess(hardware_id, id=user_id) + + def remove_virtual_access(self, user_id, virtual_id): + """Remove hardware from a portal user’s virtual guests access list. + + :param int user_id: + :param int hardware_id + + :returns: true + """ + return self.user_service.removeVirtualGuestAccess(virtual_id, id=user_id) + + def remove_dedicated_access(self, user_id, dedicated_id): + """Remove hardware from a portal user’s dedicated host access list. + + :param int user_id: + :param int dedicated_id + + :returns: true + """ + return self.user_service.removeDedicatedHostAccess(dedicated_id, id=user_id) + def _keyname_search(haystack, needle): for item in haystack: diff --git a/docs/cli/users.rst b/docs/cli/users.rst index 5195a7788..68552393c 100644 --- a/docs/cli/users.rst +++ b/docs/cli/users.rst @@ -48,4 +48,8 @@ Version 5.6.0 introduces the ability to interact with user accounts from the cli :prog: user vpn-subnet :show-nested: +.. click:: SoftLayer.CLI.user.remove_access:cli + :prog: user remove-access + :show-nested: + diff --git a/tests/CLI/modules/user_tests.py b/tests/CLI/modules/user_tests.py index a11a94838..bbd39ed1b 100644 --- a/tests/CLI/modules/user_tests.py +++ b/tests/CLI/modules/user_tests.py @@ -336,3 +336,15 @@ def test_edit_notification_off_failure(self, click): click.secho.assert_called_with('Failed to update notifications: Test notification', fg='red') self.assert_no_fail(result) self.assert_called_with('SoftLayer_Email_Subscription', 'disable', identifier=111) + + def test_remove_access_hardware(self): + result = self.run_command(['user', 'remove-access', '123456', '--hardware', '147258']) + self.assert_no_fail(result) + + def test_remove_access_virtual(self): + result = self.run_command(['user', 'remove-access', '123456', '--virtual', '987456']) + self.assert_no_fail(result) + + def test_remove_access_dedicated(self): + result = self.run_command(['user', 'remove-access', '123456', '--dedicated', '369852']) + self.assert_no_fail(result) diff --git a/tests/managers/user_tests.py b/tests/managers/user_tests.py index 5ea4d2696..ee7bb6b02 100644 --- a/tests/managers/user_tests.py +++ b/tests/managers/user_tests.py @@ -294,3 +294,15 @@ def test_gather_notifications_fail(self): self.manager.gather_notifications, ['Test not exit']) self.assertEqual("Test not exit is not a valid notification name", str(ex)) + + def test_remove_hardware(self): + self.manager.remove_hardware_access(123456, 369852) + self.assert_called_with('SoftLayer_User_Customer', 'removeHardwareAccess') + + def test_remove_virtual(self): + self.manager.remove_virtual_access(123456, 369852) + self.assert_called_with('SoftLayer_User_Customer', 'removeVirtualGuestAccess') + + def test_remove_dedicated(self): + self.manager.remove_dedicated_access(123456, 369852) + self.assert_called_with('SoftLayer_User_Customer', 'removeDedicatedHostAccess') From e4e1ea46170a33625539bf3b971fdb50d805bee8 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 8 Aug 2022 18:50:34 -0400 Subject: [PATCH 065/710] fix the team code review comments --- SoftLayer/CLI/user/remove_access.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/user/remove_access.py b/SoftLayer/CLI/user/remove_access.py index 24cfa67bb..f0d2bb01a 100644 --- a/SoftLayer/CLI/user/remove_access.py +++ b/SoftLayer/CLI/user/remove_access.py @@ -17,7 +17,10 @@ help="dedicated host ID ") @environment.pass_env def cli(env, identifier, hardware, virtual, dedicated): - """User details.""" + """Remove access from a user to an specific device. + + Example: slcli user remove-access 123456 --hardware 123456789 + """ mgr = SoftLayer.UserManager(env.client) device = '' @@ -36,3 +39,8 @@ def cli(env, identifier, hardware, virtual, dedicated): if result: click.secho("Remove to access to device: %s" % device, fg='green') + else: + raise SoftLayer.exceptions.SoftLayerError('You need argument a hardware, virtual or dedicated identifier.\n' + 'E.g slcli user 123456 --hardware 91803794\n' + ' slcli user 123456 --dedicated 91803793\n' + ' slcli user 123456 --virtual 91803792') From f593d30869e73dab396692ac9d9d2851f34aad65 Mon Sep 17 00:00:00 2001 From: edsonarios Date: Tue, 9 Aug 2022 08:44:10 -0400 Subject: [PATCH 066/710] Updated command - slcli vlan detail - added table trunks --- SoftLayer/CLI/vlan/detail.py | 41 ++++++++++++++++++++++++++++++++--- SoftLayer/managers/network.py | 15 +++++++++---- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/SoftLayer/CLI/vlan/detail.py b/SoftLayer/CLI/vlan/detail.py index b8e0bd2f9..18060f281 100644 --- a/SoftLayer/CLI/vlan/detail.py +++ b/SoftLayer/CLI/vlan/detail.py @@ -18,14 +18,23 @@ @click.option('--no-hardware', is_flag=True, help="Hide hardware listing") +@click.option('--no-trunks', + is_flag=True, + help="Hide devices with trunks") @environment.pass_env -def cli(env, identifier, no_vs, no_hardware): +def cli(env, identifier, no_vs, no_hardware, no_trunks): """Get details about a VLAN.""" - mgr = SoftLayer.NetworkManager(env.client) vlan_id = helpers.resolve_id(mgr.resolve_vlan_ids, identifier, 'VLAN') - vlan = mgr.get_vlan(vlan_id) + + mask = """mask[firewallInterfaces,primaryRouter[id, fullyQualifiedDomainName, datacenter], + totalPrimaryIpAddressCount,networkSpace,billingItem,hardware,subnets,virtualGuests, + networkVlanFirewall[id,fullyQualifiedDomainName,primaryIpAddress],attachedNetworkGateway[id,name,networkFirewall], + networkComponentTrunks[networkComponent[downlinkComponent[networkComponentGroup[membersDescription], + hardware[tagReferences]]]]]""" + + vlan = mgr.get_vlan(vlan_id, mask=mask) table = formatting.KeyValueTable(['name', 'value']) table.align['name'] = 'r' @@ -79,6 +88,21 @@ def cli(env, identifier, no_vs, no_hardware): else: table.add_row(['hardware', 'none']) + if not no_trunks: + if vlan.get('networkComponentTrunks'): + trunks = filter_trunks(vlan.get('networkComponentTrunks')) + trunks_table = formatting.Table(['device', 'port', 'tags']) + for trunk in trunks: + trunks_table.add_row([utils.lookup(trunk, 'networkComponent', 'downlinkComponent', + 'hardware', 'fullyQualifiedDomainName'), + utils.lookup(trunk, 'networkComponent', 'downlinkComponent', + 'networkComponentGroup', 'membersDescription'), + formatting.tags(utils.lookup(trunk, 'networkComponent', 'downlinkComponent', + 'hardware', 'tagReferences'))]) + table.add_row(['trunks', trunks_table]) + else: + table.add_row(['trunks', 'none']) + env.fout(table) @@ -92,3 +116,14 @@ def get_gateway_firewall(vlan): if gateway: return gateway return formatting.blank() + + +def filter_trunks(trunks): + """Filter duplicates devices with trunks of the vlan.""" + trunk_filters = [] + hardware_id = [] + for trunk in trunks: + if utils.lookup(trunk, 'networkComponent', 'downlinkComponent', 'hardwareId') not in hardware_id: + trunk_filters.append(trunk) + hardware_id.append(utils.lookup(trunk, 'networkComponent', 'downlinkComponent', 'hardwareId')) + return trunk_filters diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index f454fe4d9..529ed9f4a 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -429,15 +429,22 @@ def get_subnet(self, subnet_id, **kwargs): return self.subnet.getObject(id=subnet_id, **kwargs) - def get_vlan(self, vlan_id): + def get_vlan(self, vlan_id, mask=None): """Returns information about a single VLAN. - :param int id: The unique identifier for the VLAN + :param int vlan_id: The unique identifier for the VLAN + :param string mask: mask for request :returns: A dictionary containing a large amount of information about the specified VLAN. - """ - return self.vlan.getObject(id=vlan_id, mask=DEFAULT_GET_VLAN_MASK) + + _mask = None + if mask: + _mask = mask + else: + _mask = DEFAULT_GET_VLAN_MASK + + return self.vlan.getObject(id=vlan_id, mask=_mask) def list_global_ips(self, version=None, identifier=None, **kwargs): """Returns a list of all global IP address records on the account. From c6a9fb7a7690cfb27da322795d5af1acf69890a9 Mon Sep 17 00:00:00 2001 From: Brian Flores Date: Tue, 9 Aug 2022 15:20:44 -0400 Subject: [PATCH 067/710] added success message --- SoftLayer/CLI/hardware/reflash_firmware.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/hardware/reflash_firmware.py b/SoftLayer/CLI/hardware/reflash_firmware.py index d04f93ed8..251681c15 100644 --- a/SoftLayer/CLI/hardware/reflash_firmware.py +++ b/SoftLayer/CLI/hardware/reflash_firmware.py @@ -23,4 +23,7 @@ def cli(env, identifier): 'reflash device firmware. Continue?' % hw_id)): raise exceptions.CLIAbort('Aborted.') - mgr.reflash_firmware(hw_id) + success = mgr.reflash_firmware(hw_id) + if success: + message = 'Successfully device firmware reflashed' + click.echo(message) From 0df0e35c9ac9adb8c2fd0b4d852ada1d32bac4bd Mon Sep 17 00:00:00 2001 From: Brian Flores Date: Tue, 9 Aug 2022 15:38:03 -0400 Subject: [PATCH 068/710] update unit test --- tests/CLI/modules/server_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index cc30c70ed..6af45ccf8 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -523,7 +523,7 @@ def test_reflash_firmware(self, confirm_mock): result = self.run_command(['server', 'reflash-firmware', '1000']) self.assert_no_fail(result) - self.assertEqual(result.output, "") + self.assertEqual(result.output, 'Successfully device firmware reflashed\n') self.assert_called_with('SoftLayer_Hardware_Server', 'createFirmwareReflashTransaction', args=((1, 1, 1)), identifier=1000) From f511ff3e0df8f1e37bbd693270854b78c671ad02 Mon Sep 17 00:00:00 2001 From: edsonarios Date: Wed, 10 Aug 2022 07:57:16 -0400 Subject: [PATCH 069/710] Solved comments --- SoftLayer/CLI/vlan/detail.py | 8 ++++---- SoftLayer/managers/network.py | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/vlan/detail.py b/SoftLayer/CLI/vlan/detail.py index 18060f281..6e16d9d8c 100644 --- a/SoftLayer/CLI/vlan/detail.py +++ b/SoftLayer/CLI/vlan/detail.py @@ -60,7 +60,7 @@ def cli(env, identifier, no_vs, no_hardware, no_trunks): # subnets.append(subnet_table) table.add_row(['subnets', subnet_table]) else: - table.add_row(['subnets', 'none']) + table.add_row(['subnets', '-']) server_columns = ['hostname', 'domain', 'public_ip', 'private_ip'] @@ -74,7 +74,7 @@ def cli(env, identifier, no_vs, no_hardware, no_trunks): vsi.get('primaryBackendIpAddress')]) table.add_row(['vs', vs_table]) else: - table.add_row(['vs', 'none']) + table.add_row(['vs', '-']) if not no_hardware: if vlan.get('hardware'): @@ -86,7 +86,7 @@ def cli(env, identifier, no_vs, no_hardware, no_trunks): hardware.get('primaryBackendIpAddress')]) table.add_row(['hardware', hw_table]) else: - table.add_row(['hardware', 'none']) + table.add_row(['hardware', '-']) if not no_trunks: if vlan.get('networkComponentTrunks'): @@ -101,7 +101,7 @@ def cli(env, identifier, no_vs, no_hardware, no_trunks): 'hardware', 'tagReferences'))]) table.add_row(['trunks', trunks_table]) else: - table.add_row(['trunks', 'none']) + table.add_row(['trunks', '-']) env.fout(table) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 529ed9f4a..1b0b6eac1 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -438,7 +438,6 @@ def get_vlan(self, vlan_id, mask=None): the specified VLAN. """ - _mask = None if mask: _mask = mask else: From e04dcca294faf4ab22da454b9c08da6febb27ec8 Mon Sep 17 00:00:00 2001 From: edsonarios Date: Wed, 10 Aug 2022 08:19:21 -0400 Subject: [PATCH 070/710] Fix bug with command - slcli cdn edit --- SoftLayer/CLI/cdn/edit.py | 13 ++++++++++--- SoftLayer/managers/cdn.py | 12 ++++++++++-- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/cdn/edit.py b/SoftLayer/CLI/cdn/edit.py index 3cc4b31f3..bd2c8b827 100644 --- a/SoftLayer/CLI/cdn/edit.py +++ b/SoftLayer/CLI/cdn/edit.py @@ -19,6 +19,10 @@ type=click.INT, help="HTTP port." ) +@click.option('--https-port', '-s', + type=click.INT, + help="HTTPS port." + ) @click.option('--origin', '-o', type=click.STRING, help="Origin server address." @@ -39,7 +43,7 @@ "the Dynamic content acceleration option is not added because this has a special configuration." ) @environment.pass_env -def cli(env, identifier, header, http_port, origin, respect_headers, cache, performance_configuration): +def cli(env, identifier, header, http_port, https_port, origin, respect_headers, cache, performance_configuration): """Edit a CDN Account. Note: You can use the hostname or uniqueId as IDENTIFIER. @@ -56,7 +60,7 @@ def cli(env, identifier, header, http_port, origin, respect_headers, cache, perf else: cache_result['cacheKeyQueryRule'] = cache[0] - cdn_result = manager.edit(cdn_id, header=header, http_port=http_port, origin=origin, + cdn_result = manager.edit(cdn_id, header=header, http_port=http_port, https_port=https_port, origin=origin, respect_headers=respect_headers, cache=cache_result, performance_configuration=performance_configuration) @@ -67,7 +71,10 @@ def cli(env, identifier, header, http_port, origin, respect_headers, cache, perf for cdn in cdn_result: table.add_row(['Create Date', cdn.get('createDate')]) table.add_row(['Header', cdn.get('header')]) - table.add_row(['Http Port', cdn.get('httpPort')]) + if cdn.get('httpPort'): + table.add_row(['Http Port', cdn.get('httpPort')]) + if cdn.get('httpsPort'): + table.add_row(['Https Port', cdn.get('httpsPort')]) table.add_row(['Origin Type', cdn.get('originType')]) table.add_row(['Performance Configuration', cdn.get('performanceConfiguration')]) table.add_row(['Protocol', cdn.get('protocol')]) diff --git a/SoftLayer/managers/cdn.py b/SoftLayer/managers/cdn.py index e7d31f44f..1c6597b4e 100644 --- a/SoftLayer/managers/cdn.py +++ b/SoftLayer/managers/cdn.py @@ -175,13 +175,14 @@ def end_date(self): """Retrieve the cdn usage metric end date.""" return self._end_date - def edit(self, identifier, header=None, http_port=None, origin=None, + def edit(self, identifier, header=None, http_port=None, https_port=None, origin=None, respect_headers=None, cache=None, performance_configuration=None): """Edit the cdn object. :param string identifier: The CDN identifier. :param header: The cdn Host header. :param http_port: The cdn HTTP port. + :param https_port: The cdn HTTPS port. :param origin: The cdn Origin server address. :param respect_headers: The cdn Respect headers. :param cache: The cdn Cache key optimization. @@ -199,10 +200,14 @@ def edit(self, identifier, header=None, http_port=None, origin=None, 'vendorName': cdn_instance_detail.get('vendorName'), 'cname': cdn_instance_detail.get('cname'), 'domain': cdn_instance_detail.get('domain'), - 'httpPort': cdn_instance_detail.get('httpPort'), 'origin': cdn_instance_detail.get('originHost'), 'header': cdn_instance_detail.get('header') } + if cdn_instance_detail.get('httpPort'): + config['httpPort'] = cdn_instance_detail.get('httpPort') + + if cdn_instance_detail.get('httpsPort'): + config['httpsPort'] = cdn_instance_detail.get('httpsPort') if header: config['header'] = header @@ -210,6 +215,9 @@ def edit(self, identifier, header=None, http_port=None, origin=None, if http_port: config['httpPort'] = http_port + if https_port: + config['httpsPort'] = https_port + if origin: config['origin'] = origin From 884e7c941b3c45708710a11ab41aed19b8e63363 Mon Sep 17 00:00:00 2001 From: Brian Flores Date: Wed, 10 Aug 2022 11:51:10 -0400 Subject: [PATCH 071/710] fix observations --- SoftLayer/CLI/hardware/reflash_firmware.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/hardware/reflash_firmware.py b/SoftLayer/CLI/hardware/reflash_firmware.py index 251681c15..89d99285e 100644 --- a/SoftLayer/CLI/hardware/reflash_firmware.py +++ b/SoftLayer/CLI/hardware/reflash_firmware.py @@ -23,7 +23,5 @@ def cli(env, identifier): 'reflash device firmware. Continue?' % hw_id)): raise exceptions.CLIAbort('Aborted.') - success = mgr.reflash_firmware(hw_id) - if success: - message = 'Successfully device firmware reflashed' - click.echo(message) + if mgr.reflash_firmware(hw_id): + click.echo('Successfully device firmware reflashed') From 75343d076b1328dc9a71eca9d5dc3a80cc4111b8 Mon Sep 17 00:00:00 2001 From: Brian Flores Date: Fri, 12 Aug 2022 10:03:38 -0400 Subject: [PATCH 072/710] added domain, status and create date columns --- SoftLayer/CLI/virt/list.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/virt/list.py b/SoftLayer/CLI/virt/list.py index 01537db54..a16a13308 100644 --- a/SoftLayer/CLI/virt/list.py +++ b/SoftLayer/CLI/virt/list.py @@ -36,9 +36,12 @@ DEFAULT_COLUMNS = [ 'id', 'hostname', + 'domain', + 'deviceStatus.name', + 'datacenter', 'primary_ip', 'backend_ip', - 'datacenter', + 'createDate', 'action', ] From 2c67ebf44e6ce5f8933467472deea23a6369c372 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 12 Aug 2022 10:08:16 -0400 Subject: [PATCH 073/710] add new command on ipsec cancel command --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/vpn/ipsec/cancel.py | 30 ++++++++++++++++++++++++ SoftLayer/managers/ipsec.py | 19 +++++++++++++++ tests/CLI/modules/ipsec_tests.py | 39 ++++++++++++++++++++++++++++++- tests/managers/ipsec_tests.py | 6 +++++ 5 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 SoftLayer/CLI/vpn/ipsec/cancel.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 501ec6603..72f0cc96b 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -213,6 +213,7 @@ ('ipsec:translation-update', 'SoftLayer.CLI.vpn.ipsec.translation.update:cli'), ('ipsec:update', 'SoftLayer.CLI.vpn.ipsec.update:cli'), ('ipsec:order', 'SoftLayer.CLI.vpn.ipsec.order:cli'), + ('ipsec:cancel', 'SoftLayer.CLI.vpn.ipsec.cancel:cli'), ('loadbal', 'SoftLayer.CLI.loadbal'), ('loadbal:detail', 'SoftLayer.CLI.loadbal.detail:cli'), diff --git a/SoftLayer/CLI/vpn/ipsec/cancel.py b/SoftLayer/CLI/vpn/ipsec/cancel.py new file mode 100644 index 000000000..ac44e0556 --- /dev/null +++ b/SoftLayer/CLI/vpn/ipsec/cancel.py @@ -0,0 +1,30 @@ +"""Cancel a dedicated server.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment + + +@click.command(cls=SoftLayer.CLI.command.SLCommand, ) +@click.argument('identifier') +@click.option('--immediate', + is_flag=True, + default=False, + help="Cancels the server immediately (instead of on the billing anniversary)") +@click.option('--reason', + help="An optional cancellation reason. See cancel-reasons for a list of available options") +@click.option('--comment', + help="An optional comment to add to the cancellation ticket") +@environment.pass_env +def cli(env, identifier, immediate, reason, comment): + """Cancel a IPSEC VPN tunnel context.""" + + manager = SoftLayer.IPSECManager(env.client) + context = manager.get_tunnel_context(identifier, mask='billingItem') + + result = manager.cancel_item(context['billingItem']['id'], immediate, reason, comment) + + if result: + env.fout("Ipsec {} was cancelled.".format(identifier)) diff --git a/SoftLayer/managers/ipsec.py b/SoftLayer/managers/ipsec.py index 1043b2f1d..7cd476e56 100644 --- a/SoftLayer/managers/ipsec.py +++ b/SoftLayer/managers/ipsec.py @@ -299,3 +299,22 @@ def order(self, datacenter, item_package): item_keynames=item_package, complex_type=complex_type, hourly=False) + + def cancel_item(self, identifier, immediate, reason, comment): + """Cancels the specified billing item Ipsec. + + Example:: + + # Cancels ipsec id 1234 + result = mgr.cancel_item(billing_item_id=1234) + + :param int billing_id: The ID of the billing item to be cancelled. + :param string reason: The reason code for the cancellation. This should come from + :func:`get_cancellation_reasons`. + :param bool immediate: If set to True, will automatically update the cancelation ticket to request + the resource be reclaimed asap. This request still has to be reviewed by a human + :param string comment: An optional comment to include with the cancellation + :returns: True on success or an exception + """ + return self.client.call('SoftLayer_Billing_Item', 'cancelItem', + True, immediate, reason, comment, id=identifier) diff --git a/tests/CLI/modules/ipsec_tests.py b/tests/CLI/modules/ipsec_tests.py index 5a7a6589b..80b6971f2 100644 --- a/tests/CLI/modules/ipsec_tests.py +++ b/tests/CLI/modules/ipsec_tests.py @@ -7,7 +7,6 @@ import json - from SoftLayer.CLI.exceptions import ArgumentError from SoftLayer.CLI.exceptions import CLIHalt from SoftLayer.fixtures import SoftLayer_Product_Order @@ -520,3 +519,41 @@ def test_ipsec_order(self): order_mock.return_value = SoftLayer_Product_Order.ipsec_placeOrder result = self.run_command(['ipsec', 'order', '-d', 'dal13']) self.assert_no_fail(result) + + def test_ipsec_cancel(self): + mock = self.set_mock('SoftLayer_Account', 'getNetworkTunnelContexts') + mock.return_value = [{ + "createDate": "2013-11-05T16:03:53-06:00", + "id": 445, + "internalPeerIpAddress": "184.172.127.9", + "modifyDate": "2022-07-19T09:34:53-06:00", + "name": "ipsec003", + "phaseOneAuthentication": "MD5", + "phaseOneDiffieHellmanGroup": 2, + "phaseOneEncryption": "3DES", + "phaseOneKeylife": 14400, + "phaseTwoAuthentication": "MD5", + "phaseTwoDiffieHellmanGroup": 2, + "phaseTwoEncryption": "3DES", + "phaseTwoKeylife": 3600, + "phaseTwoPerfectForwardSecrecy": 1, + "billingItem": { + "allowCancellationFlag": 1, + "categoryCode": "network_tunnel", + "createDate": "2022-07-19T09:34:52-06:00", + "cycleStartDate": "2022-08-03T23:07:43-06:00", + "description": "IPSEC - Standard", + "id": 977194617, + "lastBillDate": "2022-08-03T23:07:43-06:00", + "modifyDate": "2022-08-03T23:07:43-06:00", + "nextBillDate": "2022-09-03T23:00:00-06:00", + "oneTimeFee": "0", + "orderItemId": 932515967, + "recurringMonths": 1, + "serviceProviderId": 1, + }}] + + mock = self.set_mock('SoftLayer_Billing_Item', 'cancelItem') + mock.return_value = True + result = self.run_command(['ipsec', 'cancel', '445', '--immediate', '--reason', 'test', '--comment', 'testcli']) + self.assert_no_fail(result) diff --git a/tests/managers/ipsec_tests.py b/tests/managers/ipsec_tests.py index 1d86f20d8..f56d0e898 100644 --- a/tests/managers/ipsec_tests.py +++ b/tests/managers/ipsec_tests.py @@ -319,3 +319,9 @@ def test_order(self): 'itemId': 1092, 'itemPriceId': '2048'}]}} self.assertEqual(result, order) + + def test_cancel_item(self): + _mock = self.set_mock('SoftLayer_Billing_Item', 'cancelItem') + _mock.return_value = True + result = self.ipsec.cancel_item(443, True, 'test', 'test') + self.assertEqual(result, True) From 8f1be5ee151c7853bbf80af0b5d6134a66c30c25 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 12 Aug 2022 10:18:56 -0400 Subject: [PATCH 074/710] add documentation --- docs/cli/ipsec.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/cli/ipsec.rst b/docs/cli/ipsec.rst index 8bfb9905f..713905a33 100644 --- a/docs/cli/ipsec.rst +++ b/docs/cli/ipsec.rst @@ -275,4 +275,8 @@ The following is an example of updating an existing address translation entry. .. click:: SoftLayer.CLI.vpn.ipsec.order:cli :prog: ipsec order + :show-nested: + +.. click:: SoftLayer.CLI.vpn.ipsec.cancel:cli + :prog: ipsec cancel :show-nested: \ No newline at end of file From 804e7c96c0ab1244e3a4f1f95ae89badd9275dea Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 18 Aug 2022 16:15:59 -0500 Subject: [PATCH 075/710] v6.1.1 release notes --- CHANGELOG.md | 48 +++++++++++++++++++++++++++++++++++++++------ SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fbc7da7d7..c6b5e22b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,46 @@ # Change Log +## [6.1.1] - 2022-08-18 + +#### What's Changed +* v6.1.0 Changelog and version bump by @allmightyspiff in https://github.com/softlayer/softlayer-python/pull/1674 +* item-list fix by @allmightyspiff in https://github.com/softlayer/softlayer-python/pull/1679 +* updating release job to actually publish to pypi by @allmightyspiff in https://github.com/softlayer/softlayer-python/pull/1680 +* Update command - slcli object-storage endpoints by @edsonarios in https://github.com/softlayer/softlayer-python/pull/1685 +* add the block volume-options command by @caberos in https://github.com/softlayer/softlayer-python/pull/1681 +* add the file volume-options command by @caberos in https://github.com/softlayer/softlayer-python/pull/1684 +* fixed issues where a message warned users about closing datacenter by @allmightyspiff in https://github.com/softlayer/softlayer-python/pull/1688 +* Enable --format=raw and fixes table width by @allmightyspiff in https://github.com/softlayer/softlayer-python/pull/1689 +* Update `slcli hardware sensor` by @BrianSantivanez in https://github.com/softlayer/softlayer-python/pull/1691 +* Improved successful response to command - slcli vs cancel by @edsonarios in https://github.com/softlayer/softlayer-python/pull/1695 +* Fixed an issue with printing tables that contained empty items by @allmightyspiff in https://github.com/softlayer/softlayer-python/pull/1697 +* Added a dependabot scanner by @allmightyspiff in https://github.com/softlayer/softlayer-python/pull/1699 +* block|file volume-options improvements by @caberos in https://github.com/softlayer/softlayer-python/pull/1700 +* Option create-options in commands hardware and dedicatedhost fixed by @edsonarios in https://github.com/softlayer/softlayer-python/pull/1703 +* pip prod(deps): bump rich from 12.3.0 to 12.5.1 by @dependabot in https://github.com/softlayer/softlayer-python/pull/1704 +* block/file volume-options improvements 2 by @caberos in https://github.com/softlayer/softlayer-python/pull/1702 +* New command ipsec order by @caberos in https://github.com/softlayer/softlayer-python/pull/1698 +* block/file volume-options improvement 3 by @caberos in https://github.com/softlayer/softlayer-python/pull/1705 +* Command slcli vlan create - displaying an error message by @edsonarios in https://github.com/softlayer/softlayer-python/pull/1707 +* New Command: user device-access by @caberos in https://github.com/softlayer/softlayer-python/pull/1712 +* Command slcli vlan edit accept that we do not send any parameters by @edsonarios in https://github.com/softlayer/softlayer-python/pull/1709 +* Updated command - slcli vlan list by @edsonarios in https://github.com/softlayer/softlayer-python/pull/1713 +* `slcli block subnets-list` command display an error message by @BrianSantivanez in https://github.com/softlayer/softlayer-python/pull/1716 +* add user remove-access command by @caberos in https://github.com/softlayer/softlayer-python/pull/1717 +* Add Devices with Trunks to vlan detail by @edsonarios in https://github.com/softlayer/softlayer-python/pull/1721 +* slcli hardware reflash-firmware command does not display success message by @BrianSantivanez in https://github.com/softlayer/softlayer-python/pull/1724 +* Fix bug with command - slcli cdn edit by @edsonarios in https://github.com/softlayer/softlayer-python/pull/1726 + +#### New Contributors +* @dependabot made their first contribution in https://github.com/softlayer/softlayer-python/pull/1704 + +**Full Changelog**: https://github.com/softlayer/softlayer-python/compare/v6.1.0...v6.1.1 + ## [6.1.0] - 2022-06-30 -## Major Updates +#### Major Updates * [Rich](https://github.com/Textualize/rich) tables by @allmightyspiff in https://github.com/softlayer/softlayer-python/pull/1646 * [Rich](https://github.com/Textualize/rich) Text support by @allmightyspiff in https://github.com/softlayer/softlayer-python/pull/1635 @@ -14,7 +50,7 @@ Rich Text and Rich Tables will modernize the output of the SLCLI to be a little ![image](https://user-images.githubusercontent.com/7408017/176753845-32af33f0-454f-4bab-ac63-1ae3db788ede.png) -## What's Changed +#### What's Changed * slcli licenses is missing the help text by @caberos in https://github.com/softlayer/softlayer-python/pull/1605 * Add a warning if user orders in a POD that is being closed by @caberos in https://github.com/softlayer/softlayer-python/pull/1600 * updated number of updates in the command account event-detail by @edsonarios in https://github.com/softlayer/softlayer-python/pull/1609 @@ -50,15 +86,15 @@ Rich Text and Rich Tables will modernize the output of the SLCLI to be a little * new feature block object-storage permissions command by @caberos in https://github.com/softlayer/softlayer-python/pull/1668 * fix the vlan table by @caberos in https://github.com/softlayer/softlayer-python/pull/1672 -## New Contributors +#### New Contributors * @BrianSantivanez made their first contribution in https://github.com/softlayer/softlayer-python/pull/1629 **Full Changelog**: https://github.com/softlayer/softlayer-python/compare/v6.0.2...v6.1.0 -## [6.0.2] - 2022-03-30 +### [6.0.2] - 2022-03-30 -## What's Changed +#### What's Changed * New Command slcli hardware|virtual monitoring by @caberos in https://github.com/softlayer/softlayer-python/pull/1593 * When listing datacenters/pods, mark those that are closing soon. by @caberos in https://github.com/softlayer/softlayer-python/pull/1597 @@ -68,7 +104,7 @@ Rich Text and Rich Tables will modernize the output of the SLCLI to be a little ## [6.0.1] - 2022-03-11 -## What's Changed +#### What's Changed * Replace the use of ptable with prettytable by @dvzrv in https://github.com/softlayer/softlayer-python/pull/1584 * Bandwidth pool management by @caberos in https://github.com/softlayer/softlayer-python/pull/1582 * Add id in the result in the command bandwidth-pools by @edsonarios in https://github.com/softlayer/softlayer-python/pull/1586 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 1fe4278a1..8ed28cacd 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v6.1.0' +VERSION = 'v6.1.1' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index a7578171e..b087359e5 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name='SoftLayer', - version='6.1.0', + version='6.1.1', description=DESCRIPTION, long_description=LONG_DESCRIPTION, long_description_content_type='text/x-rst', From c9e9d594b0e973b316982b27234e4acb3155109c Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 22 Aug 2022 12:05:46 -0400 Subject: [PATCH 076/710] Deprecate slcli hw guests --- SoftLayer/CLI/hardware/guests.py | 40 ------------------- .../fixtures/SoftLayer_Hardware_Server.py | 20 ---------- SoftLayer/managers/hardware.py | 12 ------ tests/CLI/modules/server_tests.py | 12 ------ tests/managers/hardware_tests.py | 32 --------------- 5 files changed, 116 deletions(-) delete mode 100644 SoftLayer/CLI/hardware/guests.py diff --git a/SoftLayer/CLI/hardware/guests.py b/SoftLayer/CLI/hardware/guests.py deleted file mode 100644 index 6a437ae47..000000000 --- a/SoftLayer/CLI/hardware/guests.py +++ /dev/null @@ -1,40 +0,0 @@ -"""Lists the Virtual Guests running on this server.""" -# :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 formatting -from SoftLayer.CLI import helpers -from SoftLayer import utils - - -@click.command(cls=SoftLayer.CLI.command.SLCommand, ) -@click.argument('identifier') -@environment.pass_env -def cli(env, identifier): - """Lists the Virtual Guests running on this server.""" - - mgr = SoftLayer.HardwareManager(env.client) - hw_id = helpers.resolve_id(mgr.resolve_ids, identifier, 'hardware') - hw_guests = mgr.get_hardware_guests(hw_id) - - if not hw_guests: - raise exceptions.CLIAbort("No Virtual Guests found.") - - table = formatting.Table(['id', 'hostname', 'CPU', 'Memory', 'Start Date', 'Status', 'powerState']) - table.sortby = 'hostname' - for guest in hw_guests: - table.add_row([ - guest['id'], - guest['hostname'], - '%i %s' % (guest['maxCpu'], guest['maxCpuUnits']), - guest['maxMemory'], - utils.clean_time(guest['createDate']), - guest['status']['keyName'], - guest['powerState']['keyName'] - ]) - - env.fout(table) diff --git a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py index 0b3a6c748..fc21a2316 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py @@ -278,26 +278,6 @@ } ] -getVirtualHost = { - "accountId": 11111, - "createDate": "2018-10-08T10:54:48-06:00", - "description": "host16.vmware.chechu.com", - "hardwareId": 22222, - "id": 33333, - "name": "host16.vmware.chechu.com", - "uuid": "00000000-0000-0000-0000-0cc11111", - "hardware": { - "accountId": 11111, - "domain": "chechu.com", - "hostname": "host16.vmware", - "id": 22222, - "hardwareStatus": { - "id": 5, - "status": "ACTIVE" - } - } -} - getUpgradeItemPrices = [ { "id": 21525, diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 9fd0959cb..be374b502 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -769,18 +769,6 @@ def get_hard_drives(self, instance_id): """ return self.hardware.getHardDrives(id=instance_id) - def get_hardware_guests(self, instance_id): - """Returns the hardware server guests. - - :param int instance_id: Id of the hardware server. - """ - mask = "mask[id]" - virtual_host = self.hardware.getVirtualHost(mask=mask, id=instance_id) - if virtual_host: - return self.client.call('SoftLayer_Virtual_Host', 'getGuests', mask='mask[powerState]', - id=virtual_host['id']) - return virtual_host - def get_hardware_item_prices(self, location): """Returns the hardware server item prices by location. diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 6af45ccf8..46e72eb2a 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -910,18 +910,6 @@ def test_create_hw_no_confirm(self, confirm_mock): self.assertEqual(result.exit_code, 2) - def test_get_hardware_guests(self): - result = self.run_command(['hw', 'guests', '123456']) - self.assert_no_fail(result) - - def test_hardware_guests_empty(self): - mock = self.set_mock('SoftLayer_Virtual_Host', 'getGuests') - mock.return_value = None - - result = self.run_command(['hw', 'guests', '123456']) - self.assertEqual(result.exit_code, 2) - self.assertIsInstance(result.exception, exceptions.CLIAbort) - @mock.patch('SoftLayer.CLI.formatting.confirm') def test_authorize_hw_no_confirm(self, confirm_mock): confirm_mock.return_value = False diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 58eac87d7..4a42910be 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -809,38 +809,6 @@ def test_get_hard_drive_empty(self): self.assertEqual([], result) - def test_get_hardware_guests_empty_virtualHost(self): - mock = self.set_mock('SoftLayer_Hardware_Server', 'getVirtualHost') - mock.return_value = None - - result = self.hardware.get_hardware_guests(1234) - - self.assertEqual(None, result) - - def test_get_hardware_guests(self): - mock = self.set_mock('SoftLayer_Virtual_Host', 'getGuests') - mock.return_value = [ - { - "accountId": 11111, - "hostname": "NSX-T Manager", - "id": 22222, - "maxCpu": 16, - "maxCpuUnits": "CORE", - "maxMemory": 49152, - "powerState": { - "keyName": "RUNNING", - "name": "Running" - }, - "status": { - "keyName": "ACTIVE", - "name": "Active" - } - }] - - result = self.hardware.get_hardware_guests(1234) - - self.assertEqual("NSX-T Manager", result[0]['hostname']) - def test_authorize_storage(self): options = self.hardware.authorize_storage(1234, "SL01SEL301234-11") From 69297586462e83398acc05f2a515b55b56a058f4 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 22 Aug 2022 12:07:44 -0400 Subject: [PATCH 077/710] remove the docs file --- docs/cli/hardware.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index 6f7ed344e..6e9e9d491 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -120,10 +120,6 @@ This function updates the firmware of a server. If already at the latest version :prog: hardware storage :show-nested: -.. click:: SoftLayer.CLI.hardware.guests:cli - :prog: hardware guests - :show-nested: - .. click:: SoftLayer.CLI.hardware.authorize_storage:cli :prog: hardware authorize-storage :show-nested: From a8da6ec5643696e320101415cc933c7a3ec43821 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 22 Aug 2022 19:10:52 -0400 Subject: [PATCH 078/710] fix the team code review comments --- SoftLayer/CLI/vpn/ipsec/cancel.py | 13 +++++++------ SoftLayer/managers/ipsec.py | 5 ++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/SoftLayer/CLI/vpn/ipsec/cancel.py b/SoftLayer/CLI/vpn/ipsec/cancel.py index ac44e0556..e082ee9d5 100644 --- a/SoftLayer/CLI/vpn/ipsec/cancel.py +++ b/SoftLayer/CLI/vpn/ipsec/cancel.py @@ -1,4 +1,4 @@ -"""Cancel a dedicated server.""" +"""Cancel an IPSec service.""" # :license: MIT, see LICENSE for more details. import click @@ -12,19 +12,20 @@ @click.option('--immediate', is_flag=True, default=False, - help="Cancels the server immediately (instead of on the billing anniversary)") + help="Cancels the service immediately (instead of on the billing anniversary)") @click.option('--reason', help="An optional cancellation reason. See cancel-reasons for a list of available options") -@click.option('--comment', - help="An optional comment to add to the cancellation ticket") @environment.pass_env -def cli(env, identifier, immediate, reason, comment): +def cli(env, identifier, immediate, reason): """Cancel a IPSEC VPN tunnel context.""" manager = SoftLayer.IPSECManager(env.client) context = manager.get_tunnel_context(identifier, mask='billingItem') - result = manager.cancel_item(context['billingItem']['id'], immediate, reason, comment) + if 'billingItem' not in context: + raise SoftLayer.SoftLayerError("Cannot locate billing. May already be cancelled.") + + result = manager.cancel_item(context['billingItem']['id'], immediate, reason) if result: env.fout("Ipsec {} was cancelled.".format(identifier)) diff --git a/SoftLayer/managers/ipsec.py b/SoftLayer/managers/ipsec.py index 7cd476e56..a725ad584 100644 --- a/SoftLayer/managers/ipsec.py +++ b/SoftLayer/managers/ipsec.py @@ -300,7 +300,7 @@ def order(self, datacenter, item_package): complex_type=complex_type, hourly=False) - def cancel_item(self, identifier, immediate, reason, comment): + def cancel_item(self, identifier, immediate, reason): """Cancels the specified billing item Ipsec. Example:: @@ -313,8 +313,7 @@ def cancel_item(self, identifier, immediate, reason, comment): :func:`get_cancellation_reasons`. :param bool immediate: If set to True, will automatically update the cancelation ticket to request the resource be reclaimed asap. This request still has to be reviewed by a human - :param string comment: An optional comment to include with the cancellation :returns: True on success or an exception """ return self.client.call('SoftLayer_Billing_Item', 'cancelItem', - True, immediate, reason, comment, id=identifier) + True, immediate, reason, id=identifier) From cfaeba90d06822f7ab67572ace2c02d88a5bab23 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 22 Aug 2022 19:15:21 -0400 Subject: [PATCH 079/710] fix the team code review comments --- tests/CLI/modules/ipsec_tests.py | 2 +- tests/managers/ipsec_tests.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/CLI/modules/ipsec_tests.py b/tests/CLI/modules/ipsec_tests.py index 80b6971f2..eb473101d 100644 --- a/tests/CLI/modules/ipsec_tests.py +++ b/tests/CLI/modules/ipsec_tests.py @@ -555,5 +555,5 @@ def test_ipsec_cancel(self): mock = self.set_mock('SoftLayer_Billing_Item', 'cancelItem') mock.return_value = True - result = self.run_command(['ipsec', 'cancel', '445', '--immediate', '--reason', 'test', '--comment', 'testcli']) + result = self.run_command(['ipsec', 'cancel', '445', '--immediate', '--reason', 'test']) self.assert_no_fail(result) diff --git a/tests/managers/ipsec_tests.py b/tests/managers/ipsec_tests.py index f56d0e898..b47ad8677 100644 --- a/tests/managers/ipsec_tests.py +++ b/tests/managers/ipsec_tests.py @@ -323,5 +323,5 @@ def test_order(self): def test_cancel_item(self): _mock = self.set_mock('SoftLayer_Billing_Item', 'cancelItem') _mock.return_value = True - result = self.ipsec.cancel_item(443, True, 'test', 'test') + result = self.ipsec.cancel_item(443, True, 'test') self.assertEqual(result, True) From 31df7adf94e377ab2a256b507a0c7bf35b1d4712 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 24 Aug 2022 10:05:02 -0400 Subject: [PATCH 080/710] New command: subnet clear-route --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/subnet/clear_route.py | 24 +++++++++++++++++++ .../fixtures/SoftLayer_Network_Subnet.py | 1 + SoftLayer/managers/network.py | 7 ++++++ docs/cli/subnet.rst | 5 ++++ tests/CLI/modules/subnet_tests.py | 4 ++++ tests/managers/network_tests.py | 10 ++++++-- 7 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 SoftLayer/CLI/subnet/clear_route.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index f652b77fc..0d4af96ca 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -333,6 +333,7 @@ ('subnet:lookup', 'SoftLayer.CLI.subnet.lookup:cli'), ('subnet:edit-ip', 'SoftLayer.CLI.subnet.edit_ip:cli'), ('subnet:route', 'SoftLayer.CLI.subnet.route:cli'), + ('subnet:clear-route', 'SoftLayer.CLI.subnet.clear_route:cli'), ('tags', 'SoftLayer.CLI.tags'), ('tags:cleanup', 'SoftLayer.CLI.tags.cleanup:cli'), diff --git a/SoftLayer/CLI/subnet/clear_route.py b/SoftLayer/CLI/subnet/clear_route.py new file mode 100644 index 000000000..d343abe0a --- /dev/null +++ b/SoftLayer/CLI/subnet/clear_route.py @@ -0,0 +1,24 @@ +"""Remove the route of your Account Owned subnets.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions + + +@click.command(cls=SoftLayer.CLI.command.SLCommand, ) +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Remove the route of your Account Owned subnets.""" + + mgr = SoftLayer.NetworkManager(env.client) + + subnet = mgr.clear_route(identifier) + + if subnet: + click.secho("The transaction to clear the route is created, routes will be updated in one or two minutes.") + else: + raise exceptions.CLIAbort('Failed to clear the route for the subnet.') diff --git a/SoftLayer/fixtures/SoftLayer_Network_Subnet.py b/SoftLayer/fixtures/SoftLayer_Network_Subnet.py index 9ecf8164e..1ce8f9163 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Subnet.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Subnet.py @@ -45,3 +45,4 @@ setTags = True cancel = True route = True +clearRoute = True diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 1b0b6eac1..327c1c805 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -880,3 +880,10 @@ def get_datacenter_by_keyname(self, keyname=None): if len(result) >= 1: return result[0] return {} + + def clear_route(self, identifier): + """Calls SoftLayer_Network_Subnet::clearRoute() + + returns true or false. + """ + return self.client.call('SoftLayer_Network_Subnet', 'clearRoute', id=identifier) diff --git a/docs/cli/subnet.rst b/docs/cli/subnet.rst index 7d22b8246..6d70d2eb8 100644 --- a/docs/cli/subnet.rst +++ b/docs/cli/subnet.rst @@ -30,6 +30,11 @@ Subnets .. click:: SoftLayer.CLI.subnet.edit_ip:cli :prog: subnet edit-ip :show-nested: + .. click:: SoftLayer.CLI.subnet.route:cli :prog: subnet route :show-nested: + +.. click:: SoftLayer.CLI.subnet.clear_route:cli + :prog: subnet clear-route + :show-nested: diff --git a/tests/CLI/modules/subnet_tests.py b/tests/CLI/modules/subnet_tests.py index 03166c9f9..a0ebf1ae7 100644 --- a/tests/CLI/modules/subnet_tests.py +++ b/tests/CLI/modules/subnet_tests.py @@ -190,3 +190,7 @@ def test_route(self): self.assert_no_fail(result) self.assertEqual(result.exit_code, 0) + + def test_clear_route(self): + result = self.run_command(['subnet', 'clear-route', '123456']) + self.assert_no_fail(result) diff --git a/tests/managers/network_tests.py b/tests/managers/network_tests.py index 04c2d8d6e..12cee4bc5 100644 --- a/tests/managers/network_tests.py +++ b/tests/managers/network_tests.py @@ -363,11 +363,11 @@ def test_list_vlans_with_filters(self): 'id': { 'operation': 'orderBy', 'options': [ - {'name': 'sort', 'value': ['ASC']}]}, + {'name': 'sort', 'value': ['ASC']}]}, 'vlanNumber': {'operation': 5}, 'name': {'operation': '_= primary-vlan'}, 'primaryRouter': { - 'datacenter': {'name': {'operation': '_= dal00'}}}} + 'datacenter': {'name': {'operation': '_= dal00'}}}} } self.assert_called_with('SoftLayer_Account', 'getNetworkVlans', filter=_filter) @@ -653,3 +653,9 @@ def test_get_datacenter_by_keyname(self): SoftLayer_Location.return_value = [] result = self.network.get_datacenter_by_keyname("TEST01") self.assertEqual(result, {}) + + def test_clear_route(self): + result = self.network.clear_route(1234) + + self.assertEqual(result, True) + self.assert_called_with('SoftLayer_Network_Subnet', 'clearRoute') From 74eda71fe51e1765eeb608e6a6d66b6f72e16a61 Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Thu, 25 Aug 2022 21:58:35 -0500 Subject: [PATCH 081/710] Upgrade to core22 --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index f8e2e2267..4925a1a7d 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -6,7 +6,7 @@ description: | license: MIT -base: core20 +base: core22 grade: stable confinement: strict From df3f828fca2cc22b9e7fd4efe854dc7d5186894f Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Thu, 25 Aug 2022 22:01:18 -0500 Subject: [PATCH 082/710] homeishome-launch added Better integration for users. --- snap/snapcraft.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 4925a1a7d..42375ded7 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -10,9 +10,14 @@ base: core22 grade: stable confinement: strict +assumes: + - command-chain + apps: slcli: command: bin/slcli + command-chain: + - bin/homeishome-launch environment: LC_ALL: C.UTF-8 plugs: @@ -34,3 +39,8 @@ parts: stage-packages: - python3 + + homeishome-launch: + plugin: nil + stage-snaps: + - homeishome-launch From 82eadd32b188c50e21a4fca569f883aa1114b235 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 29 Aug 2022 16:03:13 -0400 Subject: [PATCH 083/710] add vs user-access command --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/virt/access.py | 25 ++++++++++++ SoftLayer/fixtures/SoftLayer_Virtual_Guest.py | 22 +++++++++- SoftLayer/managers/vs.py | 11 +++++ docs/cli/vs.rst | 4 ++ tests/CLI/modules/vs/vs_tests.py | 40 ++++++++++--------- tests/managers/vs/vs_tests.py | 13 ++++-- 7 files changed, 93 insertions(+), 23 deletions(-) create mode 100644 SoftLayer/CLI/virt/access.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index f652b77fc..3f86ea39d 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -52,6 +52,7 @@ ('virtual:capacity', 'SoftLayer.CLI.virt.capacity:cli'), ('virtual:placementgroup', 'SoftLayer.CLI.virt.placementgroup:cli'), ('virtual:migrate', 'SoftLayer.CLI.virt.migrate:cli'), + ('virtual:access', 'SoftLayer.CLI.virt.access:cli'), ('virtual:monitoring', 'SoftLayer.CLI.virt.monitoring:cli'), ('dedicatedhost', 'SoftLayer.CLI.dedicatedhost'), diff --git a/SoftLayer/CLI/virt/access.py b/SoftLayer/CLI/virt/access.py new file mode 100644 index 000000000..1c5f44dbb --- /dev/null +++ b/SoftLayer/CLI/virt/access.py @@ -0,0 +1,25 @@ +"""Command lines which modify power states.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +@click.command(cls=SoftLayer.CLI.command.SLCommand, ) +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Get user access details a virtual server.""" + + vsi = SoftLayer.VSManager(env.client) + access_logs = vsi.browser_access_log(identifier) + + table = formatting.KeyValueTable(['Username', 'Source IP', 'Port', 'Date', 'Event', 'Message']) + for log in access_logs: + table.add_row([log.get('username'), log.get('sourceIp'), log.get('sourcePort'), log.get('createDate'), + log.get('eventType'), log.get('message')]) + + env.fout(table) diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py index 3c472b8d2..50568cd2c 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py @@ -317,7 +317,7 @@ 'item': {'description': '2 GB'}, 'hourlyRecurringFee': '.06', 'recurringFee': '42' - }, + }, 'template': {'maxMemory': 2048} }, { @@ -920,3 +920,23 @@ "createDate": "2021-03-22T13:15:31-06:00", "id": 1234567 } + +getBrowserConsoleAccessLogs = [ + { + "createDate": "2022-03-07T05:32:23-06:00", + "eventType": "CONNECTED", + "id": 509636, + "message": "User connected", + "sourceIp": "44.200.9.0", + "sourcePort": 47097, + "username": "307608_sebastian.chaparro@ibm.com" + }, + { + "createDate": "2022-03-07T07:58:27-06:00", + "eventType": "CONNECTED", + "id": 509024, + "message": "User connected", + "sourceIp": "44.200.9.0", + "sourcePort": 41185, + "username": "307608_sebastian.chaparro@ibm.com" + }] diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index d90ab5638..0ef09b2e6 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -1447,3 +1447,14 @@ def attach_portable_storage(self, vs_id, portable_id): disk_id, id=vs_id) return result + + def browser_access_log(self, identifier): + """A virtual guest’s browser access logs. + + :param int identifier: Virtual server id. + + + :return: SoftLayer_Virtual_BrowserConsoleAccessLog. + """ + mask = 'createDate,eventType,id,message,sourceIp,sourcePort,username' + return self.client.call('SoftLayer_Virtual_Guest', 'getBrowserConsoleAccessLogs', mask=mask, id=identifier) diff --git a/docs/cli/vs.rst b/docs/cli/vs.rst index 3dd34a405..c387d00a0 100644 --- a/docs/cli/vs.rst +++ b/docs/cli/vs.rst @@ -275,6 +275,10 @@ If no timezone is specified, IMS local time (CST) will be assumed, which might n :prog: virtual monitoring :show-nested: +.. click:: SoftLayer.CLI.virt.access:cli + :prog: virtual access + :show-nested: + Manages the migration of virutal guests. Supports migrating virtual guests on Dedicated Hosts as well. Reserved Capacity diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 50badc25c..e71067efe 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -345,19 +345,19 @@ def test_dns_sync_both(self, confirm_mock): 'getResourceRecords') getResourceRecords.return_value = [] createAargs = ({ - 'type': 'a', - 'host': 'vs-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '172.16.240.2', - 'ttl': 7200 - },) + 'type': 'a', + 'host': 'vs-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '172.16.240.2', + 'ttl': 7200 + },) createPTRargs = ({ - 'type': 'ptr', - 'host': '2', - 'domainId': 123456, - 'data': 'vs-test1.test.sftlyr.ws', - 'ttl': 7200 - },) + 'type': 'ptr', + 'host': '2', + 'domainId': 123456, + 'data': 'vs-test1.test.sftlyr.ws', + 'ttl': 7200 + },) result = self.run_command(['vs', 'dns-sync', '100']) @@ -400,12 +400,12 @@ def test_dns_sync_v6(self, confirm_mock): } } createV6args = ({ - 'type': 'aaaa', - 'host': 'vs-test1', - 'domainId': 12345, - 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', - 'ttl': 7200 - },) + 'type': 'aaaa', + 'host': 'vs-test1', + 'domainId': 12345, + 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', + 'ttl': 7200 + },) guest.return_value = test_guest result = self.run_command(['vs', 'dns-sync', '--aaaa-record', '100']) self.assert_no_fail(result) @@ -957,3 +957,7 @@ def test_last_transaction_empty(self): mock.return_value = vg_return result = self.run_command(['vs', 'detail', '100']) self.assert_no_fail(result) + + def test_user_access(self): + result = self.run_command(['vs', 'access', '100']) + self.assert_no_fail(result) diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index 976a66fd8..18c94d7fb 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -939,10 +939,10 @@ def test_edit_full(self): self.assertEqual(result, True) args = ({ - 'hostname': 'new-host', - 'domain': 'new.sftlyr.ws', - 'notes': 'random notes', - },) + 'hostname': 'new-host', + 'domain': 'new.sftlyr.ws', + 'notes': 'random notes', + },) self.assert_called_with('SoftLayer_Virtual_Guest', 'editObject', identifier=100, args=args) @@ -1325,3 +1325,8 @@ def test_authorize_storage_empty(self): def test_authorize_portable_storage(self): options = self.vs.attach_portable_storage(1234, 1234567) self.assertEqual(1234567, options['id']) + + def test_browser_access_log(self): + result = self.vs.browser_access_log(1234) + self.assertTrue(result) + self.assert_called_with('SoftLayer_Virtual_Guest', 'getBrowserConsoleAccessLogs', identifier=1234) From d2098d336ba4ac21069a1b311938cde783a1ae71 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 30 Aug 2022 10:42:24 -0400 Subject: [PATCH 084/710] fix the tox tool --- SoftLayer/CLI/routes.py | 1 - 1 file changed, 1 deletion(-) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index f652b77fc..b5a9b537f 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -272,7 +272,6 @@ ('hardware:detail', 'SoftLayer.CLI.hardware.detail:cli'), ('hardware:billing', 'SoftLayer.CLI.hardware.billing:cli'), ('hardware:edit', 'SoftLayer.CLI.hardware.edit:cli'), - ('hardware:guests', 'SoftLayer.CLI.hardware.guests:cli'), ('hardware:list', 'SoftLayer.CLI.hardware.list:cli'), ('hardware:power-cycle', 'SoftLayer.CLI.hardware.power:power_cycle'), ('hardware:power-off', 'SoftLayer.CLI.hardware.power:power_off'), From 8246e360478a9cf2aea8cca379c5384b0cf2d4df Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 30 Aug 2022 15:58:28 -0400 Subject: [PATCH 085/710] Remove real usersnames from test fixtrues --- SoftLayer/fixtures/SoftLayer_Account.py | 2 +- .../SoftLayer_Network_Application_Delivery_Controller.py | 2 +- .../SoftLayer_Network_Storage_Hub_Cleversafe_Account.py | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index 28359b7da..305084f87 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -845,7 +845,7 @@ 'createDate': '2015-05-05T16:23:52-06:00', 'id': 11449, 'modifyDate': '2015-05-05T16:24:09-06:00', - 'name': 'SLADC307608-1', + 'name': 'TEST307608-1', 'typeId': 2, 'description': 'Citrix NetScaler VPX 10.5 10Mbps Standard', 'managementIpAddress': '10.11.11.112', diff --git a/SoftLayer/fixtures/SoftLayer_Network_Application_Delivery_Controller.py b/SoftLayer/fixtures/SoftLayer_Network_Application_Delivery_Controller.py index 5f29c915f..924c464fe 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Application_Delivery_Controller.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Application_Delivery_Controller.py @@ -3,7 +3,7 @@ 'createDate': '2015-05-05T16:23:52-06:00', 'id': 11449, 'modifyDate': '2015-05-05T16:24:09-06:00', - 'name': 'SLADC307608-1', + 'name': 'TEST307608-1', 'typeId': 2, 'description': 'Citrix NetScaler VPX 10.5 10Mbps Standard', 'managementIpAddress': '10.11.11.112', diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage_Hub_Cleversafe_Account.py b/SoftLayer/fixtures/SoftLayer_Network_Storage_Hub_Cleversafe_Account.py index 75b71e015..c91b00b10 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Storage_Hub_Cleversafe_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage_Hub_Cleversafe_Account.py @@ -67,12 +67,12 @@ getObject = { 'id': 123456, - 'username': 'SLOSC307608-1', + 'username': 'TEST307608-1', 'credentials': [ { 'id': 1933496, - 'password': 'Um1Bp420FIFNvAg2QHjn5Sci2c2x4RNDXpVDDvnf', - 'username': 'Kv9aNIhtNa7ZRceCTgep', + 'password': 'Um1Bp420FIFNvAg2QHjn5Sci2c2x4RNDXpVDDvnfsdsd1010', + 'username': 'Kv9aNIhtNa7ZRceabecs', 'type': { 'description': 'A credential for generating S3 Compatible Signatures.', 'keyName': 'S3_COMPATIBLE_SIGNATURE' From 2c52f4618929dafd2be5f665299bf8b265addf33 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 30 Aug 2022 17:29:43 -0400 Subject: [PATCH 086/710] Fix tox request.get hangout issue --- SoftLayer/CLI/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index e79fc2880..904276545 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -44,7 +44,7 @@ def get_latest_version(): """Gets the latest version of the Softlayer library.""" try: - result = requests.get('https://pypi.org/pypi/SoftLayer/json') + result = requests.get('https://pypi.org/pypi/SoftLayer/json', timeout=0.001) json_result = result.json() latest = 'v{}'.format(json_result['info']['version']) except Exception: From f60d60edab4b6ff3824bfc0641e39b000ae5adfe Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 30 Aug 2022 17:39:55 -0400 Subject: [PATCH 087/710] Fix tox request.get hangout issue --- SoftLayer/CLI/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index 904276545..5425882da 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -44,7 +44,7 @@ def get_latest_version(): """Gets the latest version of the Softlayer library.""" try: - result = requests.get('https://pypi.org/pypi/SoftLayer/json', timeout=0.001) + result = requests.get('https://pypi.org/pypi/SoftLayer/json', timeout=60) json_result = result.json() latest = 'v{}'.format(json_result['info']['version']) except Exception: From b356690e0bb0822586bece6decf21643efbe3248 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 6 Sep 2022 11:16:11 -0400 Subject: [PATCH 088/710] Update Help message for commands that take in multiple arguments --- SoftLayer/CLI/block/access/authorize.py | 10 ++++++---- SoftLayer/CLI/file/access/authorize.py | 12 +++++++----- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/SoftLayer/CLI/block/access/authorize.py b/SoftLayer/CLI/block/access/authorize.py index f879d7590..aba08cf89 100644 --- a/SoftLayer/CLI/block/access/authorize.py +++ b/SoftLayer/CLI/block/access/authorize.py @@ -6,17 +6,19 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions +MULTIPLE = '(Multiple allowed)' + @click.command(cls=SoftLayer.CLI.command.SLCommand, ) @click.argument('volume_id') @click.option('--hardware-id', '-h', multiple=True, - help='The id of one SoftLayer_Hardware to authorize') + help='The id of one SoftLayer_Hardware to authorize ' + MULTIPLE) @click.option('--virtual-id', '-v', multiple=True, - help='The id of one SoftLayer_Virtual_Guest to authorize') + help='The id of one SoftLayer_Virtual_Guest to authorize ' + MULTIPLE) @click.option('--ip-address-id', '-i', multiple=True, - help='The id of one SoftLayer_Network_Subnet_IpAddress to authorize') + help='The id of one SoftLayer_Network_Subnet_IpAddress to authorize ' + MULTIPLE) @click.option('--ip-address', multiple=True, - help='An IP address to authorize') + help='An IP address to authorize ' + MULTIPLE) @environment.pass_env def cli(env, volume_id, hardware_id, virtual_id, ip_address_id, ip_address): """Authorizes hosts to access a given volume""" diff --git a/SoftLayer/CLI/file/access/authorize.py b/SoftLayer/CLI/file/access/authorize.py index 7e6bb1945..a927b4a8c 100644 --- a/SoftLayer/CLI/file/access/authorize.py +++ b/SoftLayer/CLI/file/access/authorize.py @@ -6,20 +6,22 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions +MULTIPLE = '(Multiple allowed)' + @click.command(cls=SoftLayer.CLI.command.SLCommand, ) @click.argument('volume_id') @click.option('--hardware-id', '-h', multiple=True, - help='The id of one SoftLayer_Hardware to authorize') + help='The id of one SoftLayer_Hardware to authorize ' + MULTIPLE) @click.option('--virtual-id', '-v', multiple=True, - help='The id of one SoftLayer_Virtual_Guest to authorize') + help='The id of one SoftLayer_Virtual_Guest to authorize ' + MULTIPLE) @click.option('--ip-address-id', '-i', multiple=True, help='The id of one SoftLayer_Network_Subnet_IpAddress' - ' to authorize') + ' to authorize ' + MULTIPLE) @click.option('--ip-address', multiple=True, - help='An IP address to authorize') + help='An IP address to authorize ' + MULTIPLE) @click.option('--subnet-id', '-s', multiple=True, - help='The id of one SoftLayer_Network_Subnet to authorize') + help='The id of one SoftLayer_Network_Subnet to authorize ' + MULTIPLE) @environment.pass_env def cli(env, volume_id, hardware_id, virtual_id, ip_address_id, ip_address, subnet_id): From c30b72fe820e8ef539f34b426fc27adf4b60511a Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 9 Sep 2022 10:02:07 -0400 Subject: [PATCH 089/710] Error with slcli order item-list --- SoftLayer/CLI/order/item_list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/order/item_list.py b/SoftLayer/CLI/order/item_list.py index 173e30a2f..9285c7380 100644 --- a/SoftLayer/CLI/order/item_list.py +++ b/SoftLayer/CLI/order/item_list.py @@ -125,7 +125,7 @@ def _location_item_prices(location_prices, location, tables): :param list tables: Table list to add location prices table. """ location_prices_table = formatting.Table(COLUMNS_ITEM_PRICES_LOCATION, title="Item Prices for %s" % location) - location_prices_table.sortby = 'keyName' + location_prices_table.sortby = 'KeyName' location_prices_table.align = 'l' for price in location_prices: cr_max = get_item_price_data(price, 'capacityRestrictionMaximum') From 310dae166ae329b4f210c6e18748e9fec66c6ca3 Mon Sep 17 00:00:00 2001 From: Brian Flores Date: Wed, 21 Sep 2022 10:47:00 -0400 Subject: [PATCH 090/710] Removed create, edit, logs, scale, and tag subcommands --- SoftLayer/CLI/autoscale/create.py | 138 --------------------------- SoftLayer/CLI/autoscale/edit.py | 58 ----------- SoftLayer/CLI/autoscale/logs.py | 38 -------- SoftLayer/CLI/autoscale/scale.py | 56 ----------- SoftLayer/CLI/autoscale/tag.py | 34 ------- SoftLayer/CLI/routes.py | 5 - tests/CLI/modules/autoscale_tests.py | 91 ------------------ 7 files changed, 420 deletions(-) delete mode 100644 SoftLayer/CLI/autoscale/create.py delete mode 100644 SoftLayer/CLI/autoscale/edit.py delete mode 100644 SoftLayer/CLI/autoscale/logs.py delete mode 100644 SoftLayer/CLI/autoscale/scale.py delete mode 100644 SoftLayer/CLI/autoscale/tag.py diff --git a/SoftLayer/CLI/autoscale/create.py b/SoftLayer/CLI/autoscale/create.py deleted file mode 100644 index 0ca1fd201..000000000 --- a/SoftLayer/CLI/autoscale/create.py +++ /dev/null @@ -1,138 +0,0 @@ -"""Order/Create a scale group.""" -# :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 formatting -from SoftLayer.CLI import helpers -from SoftLayer.managers.autoscale import AutoScaleManager - - -@click.command() -@click.option('--name', required=True, prompt=True, help="Scale group's name.") -@click.option('--cooldown', required=True, prompt=True, type=click.INT, - help="The number of seconds this group will wait after lastActionDate before performing another action.") -@click.option('--min', 'minimum', required=True, prompt=True, type=click.INT, help="Set the minimum number of guests") -@click.option('--max', 'maximum', required=True, prompt=True, type=click.INT, help="Set the maximum number of guests") -@click.option('--regional', required=True, prompt=True, type=click.INT, - help="The identifier of the regional group this scaling group is assigned to.") -@click.option('--postinstall', '-i', help="Post-install script to download") -@click.option('--os', '-o', required=True, prompt=True, help="OS install code. Tip: you can specify _LATEST") -@click.option('--datacenter', '-d', required=True, prompt=True, help="Datacenter shortname") -@click.option('--hostname', '-H', required=True, prompt=True, help="Host portion of the FQDN") -@click.option('--domain', '-D', required=True, prompt=True, help="Domain portion of the FQDN") -@click.option('--cpu', required=True, prompt=True, type=click.INT, - help="Number of CPUs for new guests (existing not effected") -@click.option('--memory', required=True, prompt=True, type=click.INT, - help="RAM in MB or GB for new guests (existing not effected") -@click.option('--policy-relative', required=True, prompt=True, - help="The type of scale to perform(ABSOLUTE, PERCENT, RELATIVE).") -@click.option('--termination-policy', - help="The termination policy for the group(CLOSEST_TO_NEXT_CHARGE=1, NEWEST=2, OLDEST=3).") -@click.option('--policy-name', help="Collection of policies for this group. This can be empty.") -@click.option('--policy-amount', help="The number to scale by. This number has different meanings based on type.") -@click.option('--userdata', help="User defined metadata string") -@helpers.multi_option('--key', '-k', help="SSH keys to add to the root user") -@helpers.multi_option('--disk', required=True, prompt=True, help="Disk sizes") -@environment.pass_env -def cli(env, **args): - """Order/Create a scale group. - - E.g. - - 'slcli autoscale create --name test --cooldown 3600 --min 1 --max 2 -o CENTOS_7_64 --datacenter dal10 - --termination-policy 2 -H testvs -D test.com --cpu 2 --memory 1024 --policy-relative absolute - --policy-name policytest --policy-amount 3 --regional 102 --disk 25 --disk 30 --disk 25' - - """ - scale = AutoScaleManager(env.client) - network = SoftLayer.NetworkManager(env.client) - - datacenter = network.get_datacenter(args.get('datacenter')) - - ssh_keys = [] - for key in args.get('key'): - resolver = SoftLayer.SshKeyManager(env.client).resolve_ids - key_id = helpers.resolve_id(resolver, key, 'SshKey') - ssh_keys.append(key_id) - scale_actions = [ - { - "amount": args['policy_amount'], - "scaleType": args['policy_relative'] - } - ] - policy_template = { - 'name': args['policy_name'], - 'scaleActions': scale_actions - - } - policies = [] - policies.append(policy_template) - block = [] - number_disk = 0 - for guest_disk in args['disk']: - if number_disk == 1: - number_disk = 2 - disks = {'diskImage': {'capacity': guest_disk}, 'device': number_disk} - block.append(disks) - number_disk += 1 - - virt_template = { - 'localDiskFlag': False, - 'domain': args['domain'], - 'hostname': args['hostname'], - 'sshKeys': ssh_keys, - 'postInstallScriptUri': args.get('postinstall'), - 'operatingSystemReferenceCode': args['os'], - 'maxMemory': args.get('memory'), - 'datacenter': {'id': datacenter[0]['id']}, - 'startCpus': args.get('cpu'), - 'blockDevices': block, - 'hourlyBillingFlag': True, - 'privateNetworkOnlyFlag': False, - 'networkComponents': [{'maxSpeed': 100}], - 'typeId': 1, - 'userData': [{ - 'value': args.get('userdata') - }], - 'networkVlans': [], - - } - - order = { - 'name': args['name'], - 'cooldown': args['cooldown'], - 'maximumMemberCount': args['maximum'], - 'minimumMemberCount': args['minimum'], - 'regionalGroupId': args['regional'], - 'suspendedFlag': False, - 'balancedTerminationFlag': False, - 'virtualGuestMemberTemplate': virt_template, - 'virtualGuestMemberCount': 0, - 'policies': policies, - 'terminationPolicyId': args['termination_policy'] - } - - if not (env.skip_confirmations or formatting.confirm( - "This action will incur charges on your account. Continue?")): - raise exceptions.CLIAbort('Aborting scale group order.') - - result = scale.create(order) - - table = formatting.KeyValueTable(['name', 'value']) - vsi_table = formatting.KeyValueTable(['Id', 'Domain', 'Hostmane']) - table.align['name'] = 'r' - table.align['value'] = 'l' - table.add_row(['Id', result['id']]) - table.add_row(['Created', result['createDate']]) - table.add_row(['Name', result['name']]) - for vsi in result['virtualGuestMembers']: - vsi_table.add_row([vsi['virtualGuest']['id'], vsi['virtualGuest']['domain'], vsi['virtualGuest']['hostname']]) - - table.add_row(['VirtualGuests', vsi_table]) - output = table - - env.fout(output) diff --git a/SoftLayer/CLI/autoscale/edit.py b/SoftLayer/CLI/autoscale/edit.py deleted file mode 100644 index a4f90076d..000000000 --- a/SoftLayer/CLI/autoscale/edit.py +++ /dev/null @@ -1,58 +0,0 @@ -"""Edits an Autoscale group.""" -# :license: MIT, see LICENSE for more details. - -import click - -from SoftLayer.CLI.command import SLCommand as SLCommand -from SoftLayer.CLI import environment -from SoftLayer.managers.autoscale import AutoScaleManager - - -@click.command(cls=SLCommand) -@click.argument('identifier') -@click.option('--name', help="Scale group's name.") -@click.option('--min', 'minimum', type=click.INT, help="Set the minimum number of guests") -@click.option('--max', 'maximum', type=click.INT, help="Set the maximum number of guests") -@click.option('--userdata', help="User defined metadata string") -@click.option('--userfile', '-F', help="Read userdata from a file", - type=click.Path(exists=True, readable=True, resolve_path=True)) -@click.option('--cpu', type=click.INT, help="Number of CPUs for new guests (existing not effected") -@click.option('--memory', type=click.INT, help="RAM in MB or GB for new guests (existing not effected") -@environment.pass_env -def cli(env, identifier, name, minimum, maximum, userdata, userfile, cpu, memory): - """Edits an Autoscale group.""" - - template = {} - autoscale = AutoScaleManager(env.client) - group = autoscale.details(identifier) - - template['name'] = name - template['minimumMemberCount'] = minimum - template['maximumMemberCount'] = maximum - virt_template = {} - if userdata: - virt_template['userData'] = [{"value": userdata}] - elif userfile: - with open(userfile, 'r', encoding="utf-8") as userfile_obj: - virt_template['userData'] = [{"value": userfile_obj.read()}] - virt_template['startCpus'] = cpu - virt_template['maxMemory'] = memory - - # Remove any entries that are `None` as the API will complain about them. - template['virtualGuestMemberTemplate'] = clean_dict(virt_template) - clean_template = clean_dict(template) - - # If there are any values edited in the template, we need to get the OLD template values and replace them. - if template['virtualGuestMemberTemplate']: - # Update old template with new values - for key, value in clean_template['virtualGuestMemberTemplate'].items(): - group['virtualGuestMemberTemplate'][key] = value - clean_template['virtualGuestMemberTemplate'] = group['virtualGuestMemberTemplate'] - - autoscale.edit(identifier, clean_template) - click.echo("Done") - - -def clean_dict(dictionary): - """Removes any `None` entires from the dictionary""" - return {k: v for k, v in dictionary.items() if v} diff --git a/SoftLayer/CLI/autoscale/logs.py b/SoftLayer/CLI/autoscale/logs.py deleted file mode 100644 index 6be461836..000000000 --- a/SoftLayer/CLI/autoscale/logs.py +++ /dev/null @@ -1,38 +0,0 @@ -"""Retreive logs for an autoscale group""" -# :license: MIT, see LICENSE for more details. - -import click - -from SoftLayer.CLI.command import SLCommand as SLCommand -from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting -from SoftLayer.managers.autoscale import AutoScaleManager -from SoftLayer import utils - - -@click.command(cls=SLCommand) -@click.argument('identifier') -@click.option('--date-min', '-d', 'date_min', type=click.DateTime(formats=["%Y-%m-%d", "%m/%d/%Y"]), - help='Earliest date to retreive logs for.') -@environment.pass_env -def cli(env, identifier, date_min): - """Retreive logs for an autoscale group""" - - autoscale = AutoScaleManager(env.client) - - mask = "mask[id,createDate,description]" - object_filter = {} - if date_min: - object_filter['logs'] = { - 'createDate': { - 'operation': 'greaterThanDate', - 'options': [{'name': 'date', 'value': [date_min.strftime("%m/%d/%Y")]}] - } - } - - logs = autoscale.get_logs(identifier, mask=mask, object_filter=object_filter) - table = formatting.Table(['Date', 'Entry'], title="Logs") - table.align['Entry'] = 'l' - for log in logs: - table.add_row([utils.clean_time(log.get('createDate')), log.get('description')]) - env.fout(table) diff --git a/SoftLayer/CLI/autoscale/scale.py b/SoftLayer/CLI/autoscale/scale.py deleted file mode 100644 index ea5b069be..000000000 --- a/SoftLayer/CLI/autoscale/scale.py +++ /dev/null @@ -1,56 +0,0 @@ -"""Scales an Autoscale group""" -# :license: MIT, see LICENSE for more details. - -import click - -from SoftLayer.CLI.command import SLCommand as SLCommand -from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting -from SoftLayer.managers.autoscale import AutoScaleManager -from SoftLayer import utils - - -@click.command(cls=SLCommand) -@click.argument('identifier') -@click.option('--up/--down', 'scale_up', is_flag=True, default=True, - help="'--up' adds guests, '--down' removes guests.") -@click.option('--by/--to', 'scale_by', is_flag=True, required=True, - help="'--by' will add/remove the specified number of guests." - " '--to' will add/remove a number of guests to get the group's guest count to the specified number.") -@click.option('--amount', required=True, type=click.INT, help="Number of guests for the scale action.") -@environment.pass_env -def cli(env, identifier, scale_up, scale_by, amount): - """Scales an Autoscale group. Bypasses a scale group's cooldown period.""" - - autoscale = AutoScaleManager(env.client) - - # Scale By, and go down, need to use negative amount - if not scale_up and scale_by: - amount = amount * -1 - - result = [] - if scale_by: - click.secho("Scaling group {} by {}".format(identifier, amount), fg='green') - result = autoscale.scale(identifier, amount) - else: - click.secho("Scaling group {} to {}".format(identifier, amount), fg='green') - result = autoscale.scale_to(identifier, amount) - - try: - # Check if the first guest has a cancellation date, assume we are removing guests if it is. - status = result[0]['virtualGuest']['status']['keyName'] or False - except (IndexError, KeyError, TypeError): - status = False - - if status == 'ACTIVE': - member_table = formatting.Table(['Id', 'Hostname', 'Created'], title="Added Guests") - else: - member_table = formatting.Table(['Id', 'Hostname', 'Created'], title="Cancelled Guests") - - for guest in result: - real_guest = guest.get('virtualGuest') - member_table.add_row([ - guest.get('id'), real_guest.get('hostname'), utils.clean_time(real_guest.get('createDate')) - ]) - - env.fout(member_table) diff --git a/SoftLayer/CLI/autoscale/tag.py b/SoftLayer/CLI/autoscale/tag.py deleted file mode 100644 index b8f959352..000000000 --- a/SoftLayer/CLI/autoscale/tag.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Tags all guests in an autoscale group.""" -# :license: MIT, see LICENSE for more details. - -import click - -from SoftLayer.CLI.command import SLCommand as SLCommand -from SoftLayer.CLI import environment -from SoftLayer.managers.autoscale import AutoScaleManager -from SoftLayer.managers.vs import VSManager - - -@click.command(cls=SLCommand) -@click.argument('identifier') -@click.option('--tags', '-g', help="Tags to set for each guest in this group. Existing tags are overwritten. " - "An empty string will remove all tags") -@environment.pass_env -def cli(env, identifier, tags): - """Tags all guests in an autoscale group. - - --tags "Use, quotes, if you, want whitespace" - - --tags Otherwise,Just,commas - """ - - autoscale = AutoScaleManager(env.client) - vsmanager = VSManager(env.client) - mask = "mask[id,virtualGuestId,virtualGuest[tagReferences,id,hostname]]" - guests = autoscale.get_virtual_guests(identifier, mask=mask) - click.echo("New Tags: {}".format(tags)) - for guest in guests: - real_guest = guest.get('virtualGuest') - click.echo("Setting tags for {}".format(real_guest.get('hostname'))) - vsmanager.set_tags(tags, real_guest.get('id'),) - click.echo("Done") diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index c85858f81..234a4c03f 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -386,12 +386,7 @@ ('autoscale', 'SoftLayer.CLI.autoscale'), ('autoscale:list', 'SoftLayer.CLI.autoscale.list:cli'), - ('autoscale:create', 'SoftLayer.CLI.autoscale.create:cli'), ('autoscale:detail', 'SoftLayer.CLI.autoscale.detail:cli'), - ('autoscale:scale', 'SoftLayer.CLI.autoscale.scale:cli'), - ('autoscale:logs', 'SoftLayer.CLI.autoscale.logs:cli'), - ('autoscale:tag', 'SoftLayer.CLI.autoscale.tag:cli'), - ('autoscale:edit', 'SoftLayer.CLI.autoscale.edit:cli'), ('autoscale:delete', 'SoftLayer.CLI.autoscale.delete:cli'), ] diff --git a/tests/CLI/modules/autoscale_tests.py b/tests/CLI/modules/autoscale_tests.py index fbdfd7f0b..2628d503c 100644 --- a/tests/CLI/modules/autoscale_tests.py +++ b/tests/CLI/modules/autoscale_tests.py @@ -8,42 +8,11 @@ Tests for the autoscale cli command """ -import sys -from unittest import mock as mock - -from SoftLayer import fixtures from SoftLayer import testing -import tempfile - class AutoscaleTests(testing.TestCase): - def test_logs_dates(self): - result = self.run_command(['autoscale', 'logs', '123456', '-d', '2019-02-02']) - print(result) - self.assert_no_fail(result) - - def test_scale_down(self): - result = self.run_command(['autoscale', 'scale', '123456', '--down', '--by', '--amount', '2']) - self.assert_no_fail(result) - - def test_scale_up(self): - result = self.run_command(['autoscale', 'scale', '123456', '--up', '--to', '--amount', '2']) - self.assert_no_fail(result) - - def test_scale_to(self): - result = self.run_command(['autoscale', 'scale', '789654123', '--down', '--to', '--amount', '2']) - self.assert_no_fail(result) - - def test_scale_by_up(self): - result = self.run_command(['autoscale', 'scale', '789654123', '--by', '--down', '--amount', '-1']) - self.assert_no_fail(result) - - def test_scale_cancel(self): - result = self.run_command(['autoscale', 'scale', '789654123', '--by', '--down', '--amount', '1']) - self.assert_no_fail(result) - def test_autoscale_list(self): result = self.run_command(['autoscale', 'list']) self.assert_no_fail(result) @@ -52,66 +21,6 @@ def test_autoscale_detail(self): result = self.run_command(['autoscale', 'detail', '12222222']) self.assert_no_fail(result) - def test_autoscale_tag(self): - result = self.run_command(['autoscale', 'tag', '12345']) - self.assert_no_fail(result) - - @mock.patch('SoftLayer.managers.autoscale.AutoScaleManager.edit') - def test_autoscale_edit(self, manager): - result = self.run_command(['autoscale', 'edit', '12345', '--name', 'test']) - self.assert_no_fail(result) - manager.assert_called_with('12345', {'name': 'test'}) - - @mock.patch('SoftLayer.managers.autoscale.AutoScaleManager.edit') - def test_autoscale_edit_userdata(self, manager): - group = fixtures.SoftLayer_Scale_Group.getObject - template = { - 'virtualGuestMemberTemplate': group['virtualGuestMemberTemplate'] - } - template['virtualGuestMemberTemplate']['userData'] = [{'value': 'test'}] - - result = self.run_command(['autoscale', 'edit', '12345', '--userdata', 'test']) - self.assert_no_fail(result) - manager.assert_called_with('12345', template) - - @mock.patch('SoftLayer.managers.autoscale.AutoScaleManager.edit') - def test_autoscale_edit_userfile(self, manager): - # On windows, python cannot edit a NamedTemporaryFile. - if (sys.platform.startswith("win")): - self.skipTest("Test doesn't work in Windows") - group = fixtures.SoftLayer_Scale_Group.getObject - template = { - 'virtualGuestMemberTemplate': group['virtualGuestMemberTemplate'] - } - template['virtualGuestMemberTemplate']['userData'] = [{'value': ''}] - - with tempfile.NamedTemporaryFile() as userfile: - result = self.run_command(['autoscale', 'edit', '12345', '--userfile', userfile.name]) - self.assert_no_fail(result) - manager.assert_called_with('12345', template) - - @mock.patch('SoftLayer.CLI.formatting.confirm') - def test_autoscale_create(self, confirm_mock): - confirm_mock.return_value = True - result = self.run_command(['autoscale', 'create', - '--name=test', - '--cooldown=3600', - '--min=1', - '--max=3', - '-o=CENTOS_7_64', - '--datacenter=ams01', - '--termination-policy=2', - '-H=testvs', - '-D=test.com', - '--cpu=2', - '--memory=1024', - '--policy-relative=absolute', - '--policy-amount=3', - '--regional=102', - '--disk=25']) - self.assert_no_fail(result) - self.assertEqual(result.exit_code, 0) - def test_autoscale_delete(self): result = self.run_command(['autoscale', 'delete', '12345']) self.assert_no_fail(result) From e6e454d7a2fe6fb811765eab8ca7b917b30db059 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 21 Sep 2022 10:48:40 -0400 Subject: [PATCH 091/710] Unhandled error running a subcommand in slcli --- SoftLayer/CLI/object_storage/credential/__init__.py | 7 +++++-- SoftLayer/CLI/virt/capacity/__init__.py | 8 +++++--- SoftLayer/CLI/virt/placementgroup/__init__.py | 7 +++++-- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/SoftLayer/CLI/object_storage/credential/__init__.py b/SoftLayer/CLI/object_storage/credential/__init__.py index 1f71754bb..37380f5c1 100644 --- a/SoftLayer/CLI/object_storage/credential/__init__.py +++ b/SoftLayer/CLI/object_storage/credential/__init__.py @@ -32,8 +32,11 @@ def get_command(self, ctx, cmd_name): """Get command for click.""" path = "%s.%s" % (__name__, cmd_name) path = path.replace("-", "_") - module = importlib.import_module(path) - return getattr(module, 'cli') + try: + module = importlib.import_module(path) + return getattr(module, 'cli') + except ModuleNotFoundError as ex: + print(ex.name) # Required to get the sub-sub-sub command to work. diff --git a/SoftLayer/CLI/virt/capacity/__init__.py b/SoftLayer/CLI/virt/capacity/__init__.py index 3f891c194..480f58260 100644 --- a/SoftLayer/CLI/virt/capacity/__init__.py +++ b/SoftLayer/CLI/virt/capacity/__init__.py @@ -37,9 +37,11 @@ def get_command(self, ctx, cmd_name): """Get command for click.""" path = "%s.%s" % (__name__, cmd_name) path = path.replace("-", "_") - module = importlib.import_module(path) - return getattr(module, 'cli') - + try: + module = importlib.import_module(path) + return getattr(module, 'cli') + except ModuleNotFoundError as ex: + print(ex.name) # Required to get the sub-sub-sub command to work. @click.group(cls=CapacityCommands, context_settings=CONTEXT) diff --git a/SoftLayer/CLI/virt/placementgroup/__init__.py b/SoftLayer/CLI/virt/placementgroup/__init__.py index aa748a5b1..c032a310c 100644 --- a/SoftLayer/CLI/virt/placementgroup/__init__.py +++ b/SoftLayer/CLI/virt/placementgroup/__init__.py @@ -36,8 +36,11 @@ def get_command(self, ctx, cmd_name): """Get command for click.""" path = "%s.%s" % (__name__, cmd_name) path = path.replace("-", "_") - module = importlib.import_module(path) - return getattr(module, 'cli') + try: + module = importlib.import_module(path) + return getattr(module, 'cli') + except ModuleNotFoundError as ex: + print(ex.name) # Required to get the sub-sub-sub command to work. From 24b10cbfcfb1334a096463a008c2ada4e23b03fe Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 21 Sep 2022 10:53:26 -0400 Subject: [PATCH 092/710] Fix the tox tool --- SoftLayer/CLI/virt/capacity/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/SoftLayer/CLI/virt/capacity/__init__.py b/SoftLayer/CLI/virt/capacity/__init__.py index 480f58260..d44814971 100644 --- a/SoftLayer/CLI/virt/capacity/__init__.py +++ b/SoftLayer/CLI/virt/capacity/__init__.py @@ -43,6 +43,7 @@ def get_command(self, ctx, cmd_name): except ModuleNotFoundError as ex: print(ex.name) + # Required to get the sub-sub-sub command to work. @click.group(cls=CapacityCommands, context_settings=CONTEXT) def cli(): From f88c7cdf2c31af04481df0cfee7fda50739f116a Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 21 Sep 2022 15:04:35 -0400 Subject: [PATCH 093/710] fix the tox tool --- SoftLayer/CLI/object_storage/credential/__init__.py | 1 + SoftLayer/CLI/virt/capacity/__init__.py | 1 + SoftLayer/CLI/virt/placementgroup/__init__.py | 1 + 3 files changed, 3 insertions(+) diff --git a/SoftLayer/CLI/object_storage/credential/__init__.py b/SoftLayer/CLI/object_storage/credential/__init__.py index 37380f5c1..f3bc2723a 100644 --- a/SoftLayer/CLI/object_storage/credential/__init__.py +++ b/SoftLayer/CLI/object_storage/credential/__init__.py @@ -37,6 +37,7 @@ def get_command(self, ctx, cmd_name): return getattr(module, 'cli') except ModuleNotFoundError as ex: print(ex.name) + return None # Required to get the sub-sub-sub command to work. diff --git a/SoftLayer/CLI/virt/capacity/__init__.py b/SoftLayer/CLI/virt/capacity/__init__.py index d44814971..21ff940ad 100644 --- a/SoftLayer/CLI/virt/capacity/__init__.py +++ b/SoftLayer/CLI/virt/capacity/__init__.py @@ -42,6 +42,7 @@ def get_command(self, ctx, cmd_name): return getattr(module, 'cli') except ModuleNotFoundError as ex: print(ex.name) + return None # Required to get the sub-sub-sub command to work. diff --git a/SoftLayer/CLI/virt/placementgroup/__init__.py b/SoftLayer/CLI/virt/placementgroup/__init__.py index c032a310c..1aa89a16e 100644 --- a/SoftLayer/CLI/virt/placementgroup/__init__.py +++ b/SoftLayer/CLI/virt/placementgroup/__init__.py @@ -41,6 +41,7 @@ def get_command(self, ctx, cmd_name): return getattr(module, 'cli') except ModuleNotFoundError as ex: print(ex.name) + return None # Required to get the sub-sub-sub command to work. From f6d6fbb5c2486ae1721ab759865dbd2862cbafab Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 23 Sep 2022 12:58:35 -0500 Subject: [PATCH 094/710] v6.1.2 release notes --- CHANGELOG.md | 20 ++++++++++++++++++++ SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6b5e22b5..ef9cc56cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Change Log +## [6.1.2] - 2022-09-23 + +#### What's Changed +* Snapcraft: Updated to Core22 and add homeishome-launch by @kz6fittycent in https://github.com/softlayer/softlayer-python/pull/1740 +* Add status, create date and domain columns in `slcli vs list command` by @BrianSantivanez in https://github.com/softlayer/softlayer-python/pull/1728 +* New command: ipsec cancel by @caberos in https://github.com/softlayer/softlayer-python/pull/1729 +* New command: subnet clear-route by @caberos in https://github.com/softlayer/softlayer-python/pull/1738 +* Deprecate slcli hw guests by @caberos in https://github.com/softlayer/softlayer-python/pull/1736 +* Remove real usersnames from test fixtrues by @caberos in https://github.com/softlayer/softlayer-python/pull/1743 +* Fix tox request.get hangout issue by @caberos in https://github.com/softlayer/softlayer-python/pull/1746 +* add vs user-access command by @caberos in https://github.com/softlayer/softlayer-python/pull/1741 +* Update Help message for commands that take in multiple arguments by @caberos in https://github.com/softlayer/softlayer-python/pull/1748 +* Error with slcli order item-list by @caberos in https://github.com/softlayer/softlayer-python/pull/1751 +* deprecate sl `autoscale` by @BrianSantivanez in https://github.com/softlayer/softlayer-python/pull/1753 +* Unhandled error running a subcommand in slcli by @caberos in https://github.com/softlayer/softlayer-python/pull/1754 + + +**Full Changelog**: https://github.com/softlayer/softlayer-python/compare/v6.1.1...v6.1.2 + + ## [6.1.1] - 2022-08-18 #### What's Changed diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 8ed28cacd..74858c3a9 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v6.1.1' +VERSION = 'v6.1.2' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index b087359e5..0af3c807d 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name='SoftLayer', - version='6.1.1', + version='6.1.2', description=DESCRIPTION, long_description=LONG_DESCRIPTION, long_description_content_type='text/x-rst', From 392c38718c172a8f76c1f53fbe23ba2659cf320c Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 23 Sep 2022 13:31:01 -0500 Subject: [PATCH 095/710] updated release workflow for the correct url --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e322a5b4a..5d0d12773 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -48,5 +48,5 @@ jobs: with: user: __token__ password: ${{ secrets.CGALLO_PYPI }} - repository_url: https://pypi.org/legacy/ + repository_url: https://upload.pypi.org/legacy/ From 57d349d8de3bf987c73d17e289c512cb49e16432 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 23 Sep 2022 15:59:10 -0400 Subject: [PATCH 096/710] New Command: Hardware notifications --- SoftLayer/CLI/hardware/add_notification.py | 31 ++++++++ SoftLayer/CLI/hardware/notifications.py | 30 ++++++++ SoftLayer/CLI/routes.py | 2 + ...yer_User_Customer_Notification_Hardware.py | 70 +++++++++++++++++++ SoftLayer/managers/hardware.py | 10 +++ docs/cli/hardware.rst | 8 +++ tests/CLI/modules/server_tests.py | 44 +++++++----- tests/managers/hardware_tests.py | 8 +++ 8 files changed, 185 insertions(+), 18 deletions(-) create mode 100644 SoftLayer/CLI/hardware/add_notification.py create mode 100644 SoftLayer/CLI/hardware/notifications.py create mode 100644 SoftLayer/fixtures/SoftLayer_User_Customer_Notification_Hardware.py diff --git a/SoftLayer/CLI/hardware/add_notification.py b/SoftLayer/CLI/hardware/add_notification.py new file mode 100644 index 000000000..eda48d149 --- /dev/null +++ b/SoftLayer/CLI/hardware/add_notification.py @@ -0,0 +1,31 @@ +"""Create a user hardware notification entry.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +@click.command(cls=SoftLayer.CLI.command.SLCommand, ) +@click.argument('identifier') +@click.option('--users', multiple=True, + help='An IP address to authorize ') +@environment.pass_env +def cli(env, identifier, users): + """Create a user hardware notification entry.""" + + hardware = SoftLayer.HardwareManager(env.client) + + table = formatting.KeyValueTable(['Id', 'Hostmane', 'Username', 'Email', 'FirstName', 'Lastname']) + table.align['Id'] = 'r' + table.align['Username'] = 'l' + + for user in users: + notification = hardware.add_notification(identifier, user) + table.add_row([notification['id'], notification['hardware']['fullyQualifiedDomainName'], + notification['user']['username'], notification['user']['email'], + notification['user']['lastName'], notification['user']['firstName']]) + + env.fout(table) diff --git a/SoftLayer/CLI/hardware/notifications.py b/SoftLayer/CLI/hardware/notifications.py new file mode 100644 index 000000000..abb1273a9 --- /dev/null +++ b/SoftLayer/CLI/hardware/notifications.py @@ -0,0 +1,30 @@ +"""Get all hardware notifications associated with the passed hardware ID.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +@click.command(cls=SoftLayer.CLI.command.SLCommand, ) +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Get all hardware notifications.""" + + hardware = SoftLayer.HardwareManager(env.client) + + notifications = hardware.get_notifications(identifier) + + table = formatting.KeyValueTable(['Domain', 'Hostmane', 'Username', 'Email', 'FirstName', 'Lastname']) + table.align['Domain'] = 'r' + table.align['Username'] = 'l' + + for notification in notifications: + table.add_row([notification['hardware']['fullyQualifiedDomainName'], notification['hardware']['hostname'], + notification['user']['username'], notification['user']['email'], + notification['user']['lastName'], notification['user']['firstName']]) + + env.fout(table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index c85858f81..9136aca76 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -292,6 +292,8 @@ ('hardware:upgrade', 'SoftLayer.CLI.hardware.upgrade:cli'), ('hardware:sensor', 'SoftLayer.CLI.hardware.sensor:cli'), ('hardware:monitoring', 'SoftLayer.CLI.hardware.monitoring:cli'), + ('hardware:notifications', 'SoftLayer.CLI.hardware.notifications:cli'), + ('hardware:add-notification', 'SoftLayer.CLI.hardware.add_notification:cli'), ('securitygroup', 'SoftLayer.CLI.securitygroup'), ('securitygroup:list', 'SoftLayer.CLI.securitygroup.list:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_User_Customer_Notification_Hardware.py b/SoftLayer/fixtures/SoftLayer_User_Customer_Notification_Hardware.py new file mode 100644 index 000000000..1441b8586 --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_User_Customer_Notification_Hardware.py @@ -0,0 +1,70 @@ +findByHardwareId = [ + { + 'hardwareId': 147258, + 'id': 1232569, + 'userId': 3698, + 'hardware': { + 'accountId': 963258, + 'domain': 'testedit.com', + 'fullyQualifiedDomainName': 'testslcli.testedit.com', + 'hostname': 'bardcabero', + 'id': 1403539, + 'notes': 'My golang note', + 'provisionDate': '2020-04-27T16:10:56-06:00', + }, + 'user': { + 'accountId': 307608, + 'email': 'cgallo@us.ibm.com', + 'firstName': 'CHRISTOPHER', + 'id': 167758, + 'lastName': 'GALLO', + 'username': 'SL307608', + } + }, + { + 'hardwareId': 1403539, + 'id': 1408587, + 'userId': 9734826, + 'hardware': { + 'accountId': 963258, + 'domain': 'testedit.com', + 'fullyQualifiedDomainName': 'testslcli.testedit.com', + 'hostname': 'bardcabero', + 'id': 1403539, + 'notes': 'My golang note', + 'provisionDate': '2020-04-27T16:10:56-06:00', + }, + 'user': { + 'accountId': 307608, + 'email': 'Brian.Flores@ibm.com', + 'firstName': 'Brian', + 'id': 9734826, + 'lastName': 'Flores', + 'username': '307608_brian.flores@ibm.com', + } + } +] + +createObject = { + 'hardwareId': 1403539, + 'id': 1408593, + 'userId': 7650493, + 'hardware': { + 'accountId': 307608, + 'domain': 'testedit.com', + 'fullyQualifiedDomainName': 'bardcabero.testedit.com', + 'hostname': 'bardcabero', + 'id': 1403539, + 'notes': 'My golang note', + + }, + 'user': { + 'accountId': 307608, + 'email': 'daniel.cabero@jalasoft.com', + 'firstName': 'daniel', + 'id': 7650493, + 'lastName': 'cabero', + 'username': 'sl307608-dcabero' + + } +} diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index be374b502..92a820cae 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -1092,6 +1092,16 @@ def get_sensors(self, hardware_id): """Returns Hardware sensor data""" return self.client.call('Hardware', 'getSensorData', id=hardware_id) + def get_notifications(self, hardware_id): + """Returns all hardware notifications.""" + return self.client.call('SoftLayer_User_Customer_Notification_Hardware', 'findByHardwareId', hardware_id) + + def add_notification(self, hardware_id, user_id): + """Create a user hardware notification entry""" + + template = {"hardwareId": hardware_id, "userId": user_id} + return self.client.call('SoftLayer_User_Customer_Notification_Hardware', 'createObject', template) + def _get_bandwidth_key(items, hourly=True, no_public=False, location=None): """Picks a valid Bandwidth Item, returns the KeyName""" diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index 6e9e9d491..dfd10e821 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -131,3 +131,11 @@ This function updates the firmware of a server. If already at the latest version .. click:: SoftLayer.CLI.hardware.sensor:cli :prog: hardware sensor :show-nested: + +.. click:: SoftLayer.CLI.hardware.notifications:cli + :prog: hardware notifications + :show-nested: + +.. click:: SoftLayer.CLI.hardware.add_notification:cli + :prog: hardware add-notification + :show-nested: diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 46e72eb2a..7df095991 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -704,19 +704,19 @@ def test_dns_sync_both(self, confirm_mock): 'getResourceRecords') getResourceRecords.return_value = [] createAargs = ({ - 'type': 'a', - 'host': 'hardware-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '172.16.1.100', - 'ttl': 7200 - },) + 'type': 'a', + 'host': 'hardware-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '172.16.1.100', + 'ttl': 7200 + },) createPTRargs = ({ - 'type': 'ptr', - 'host': '100', - 'domainId': 123456, - 'data': 'hardware-test1.test.sftlyr.ws', - 'ttl': 7200 - },) + 'type': 'ptr', + 'host': '100', + 'domainId': 123456, + 'data': 'hardware-test1.test.sftlyr.ws', + 'ttl': 7200 + },) result = self.run_command(['hw', 'dns-sync', '1000']) @@ -759,12 +759,12 @@ def test_dns_sync_v6(self, confirm_mock): } } createV6args = ({ - 'type': 'aaaa', - 'host': 'hardware-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', - 'ttl': 7200 - },) + 'type': 'aaaa', + 'host': 'hardware-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', + 'ttl': 7200 + },) server.return_value = test_server result = self.run_command(['hw', 'dns-sync', '--aaaa-record', '1000']) self.assert_no_fail(result) @@ -1026,3 +1026,11 @@ def test_check_for_closing(self, confirm_mock): '--flavor', 'B1_2X8X25', '--datacenter', 'mex01', '--os', 'UBUNTU_LATEST']) self.assert_no_fail(result) self.assertNotIn('Warning: Closed soon: mex01', result.output) + + def test_notifications(self): + result = self.run_command(['hardware', 'notifications', '100']) + self.assert_no_fail(result) + + def test_add_notification(self): + result = self.run_command(['hardware', 'add-notification', '100', '--users', '123456']) + self.assert_no_fail(result) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 4a42910be..b4e177bd4 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -920,6 +920,14 @@ def test_sensor(self): self.hardware.get_sensors(100) self.assert_called_with('SoftLayer_Hardware', 'getSensorData') + def test_notification(self): + self.hardware.get_notifications(100) + self.assert_called_with('SoftLayer_User_Customer_Notification_Hardware', 'findByHardwareId') + + def test_add_notification(self): + self.hardware.add_notification(100, 123456) + self.assert_called_with('SoftLayer_User_Customer_Notification_Hardware', 'createObject') + class HardwareHelperTests(testing.TestCase): From 087ca0de7e622d21cdae4e940ec57858bbcd7804 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 27 Sep 2022 11:48:50 -0400 Subject: [PATCH 097/710] New Command: virtual notifications --- SoftLayer/CLI/routes.py | 2 + SoftLayer/CLI/virt/add_notification.py | 31 +++++++++++ SoftLayer/CLI/virt/notifications.py | 30 ++++++++++ ...ser_Customer_Notification_Virtual_Guest.py | 55 +++++++++++++++++++ SoftLayer/managers/vs.py | 12 ++++ docs/cli/vs.rst | 8 +++ tests/CLI/modules/vs/vs_tests.py | 8 +++ tests/managers/vs/vs_tests.py | 8 +++ 8 files changed, 154 insertions(+) create mode 100644 SoftLayer/CLI/virt/add_notification.py create mode 100644 SoftLayer/CLI/virt/notifications.py create mode 100644 SoftLayer/fixtures/SoftLayer_User_Customer_Notification_Virtual_Guest.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index c85858f81..63ee6483b 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -54,6 +54,8 @@ ('virtual:migrate', 'SoftLayer.CLI.virt.migrate:cli'), ('virtual:access', 'SoftLayer.CLI.virt.access:cli'), ('virtual:monitoring', 'SoftLayer.CLI.virt.monitoring:cli'), + ('virtual:notifications', 'SoftLayer.CLI.virt.notifications:cli'), + ('virtual:add-notification', 'SoftLayer.CLI.virt.add_notification:cli'), ('dedicatedhost', 'SoftLayer.CLI.dedicatedhost'), ('dedicatedhost:list', 'SoftLayer.CLI.dedicatedhost.list:cli'), diff --git a/SoftLayer/CLI/virt/add_notification.py b/SoftLayer/CLI/virt/add_notification.py new file mode 100644 index 000000000..45bf70a26 --- /dev/null +++ b/SoftLayer/CLI/virt/add_notification.py @@ -0,0 +1,31 @@ +"""Create a user virtual notification entry.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +@click.command(cls=SoftLayer.CLI.command.SLCommand, ) +@click.argument('identifier') +@click.option('--users', multiple=True, + help='UserId to be notified on monitoring failure.') +@environment.pass_env +def cli(env, identifier, users): + """Create a user virtual notification entry.""" + + virtual = SoftLayer.VSManager(env.client) + + table = formatting.KeyValueTable(['Id', 'Hostmane', 'Username', 'Email', 'FirstName', 'Lastname']) + table.align['Id'] = 'r' + table.align['Username'] = 'l' + + for user in users: + notification = virtual.add_notification(identifier, user) + table.add_row([notification['id'], notification['guest']['fullyQualifiedDomainName'], + notification['user']['username'], notification['user']['email'], + notification['user']['lastName'], notification['user']['firstName']]) + + env.fout(table) diff --git a/SoftLayer/CLI/virt/notifications.py b/SoftLayer/CLI/virt/notifications.py new file mode 100644 index 000000000..103c56956 --- /dev/null +++ b/SoftLayer/CLI/virt/notifications.py @@ -0,0 +1,30 @@ +"""Get all hardware notifications associated with the passed hardware ID.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +@click.command(cls=SoftLayer.CLI.command.SLCommand, ) +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Get all hardware notifications.""" + + virtual = SoftLayer.VSManager(env.client) + + notifications = virtual.get_notifications(identifier) + + table = formatting.KeyValueTable(['Domain', 'Hostmane', 'Username', 'Email', 'FirstName', 'Lastname']) + table.align['Domain'] = 'r' + table.align['Username'] = 'l' + + for notification in notifications: + table.add_row([notification['guest']['fullyQualifiedDomainName'], notification['guest']['hostname'], + notification['user']['username'], notification['user']['email'], + notification['user']['lastName'], notification['user']['firstName']]) + + env.fout(table) diff --git a/SoftLayer/fixtures/SoftLayer_User_Customer_Notification_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_User_Customer_Notification_Virtual_Guest.py new file mode 100644 index 000000000..0164c5a28 --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_User_Customer_Notification_Virtual_Guest.py @@ -0,0 +1,55 @@ +findByGuestId = [ + { + 'guestId': 100, + 'id': 123, + 'userId': 147852, + 'guest': { + 'domain': 'test.support.com', + 'fullyQualifiedDomainName': 'adns.test.support.com', + 'hostname': 'adns', + 'id': 123, + 'maxCpu': 4, + 'maxCpuUnits': 'CORE', + 'maxMemory': 16384, + 'startCpus': 4, + 'statusId': 1001, + 'uuid': 'b395782d-2144-1f9c-0a90-28a7a5160d81', + }, + 'user': { + 'accountId': 147852, + 'displayName': 'TESTSUPPORT', + 'email': 'testsupport@us.ibm.com', + 'firstName': 'test', + 'id': 167758, + 'lastName': 'support', + 'username': 'test', + } + } +] + +createObject = { + 'guestId': 100, + 'id': 123, + 'userId': 147852, + 'guest': { + 'domain': 'test.support.com', + 'fullyQualifiedDomainName': 'adns.test.support.com', + 'hostname': 'adns', + 'id': 123, + 'maxCpu': 4, + 'maxCpuUnits': 'CORE', + 'maxMemory': 16384, + 'startCpus': 4, + 'statusId': 1001, + 'uuid': 'b395782d-2144-1f9c-0a90-28a7a5160d81', + }, + 'user': { + 'accountId': 147852, + 'displayName': 'TESTSUPPORT', + 'email': 'testsupport@us.ibm.com', + 'firstName': 'test', + 'id': 167758, + 'lastName': 'support', + 'username': 'test', + } +} diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 0ef09b2e6..06847d42d 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -1458,3 +1458,15 @@ def browser_access_log(self, identifier): """ mask = 'createDate,eventType,id,message,sourceIp,sourcePort,username' return self.client.call('SoftLayer_Virtual_Guest', 'getBrowserConsoleAccessLogs', mask=mask, id=identifier) + + def get_notifications(self, vs_id): + """Returns all virtual notifications.""" + return self.client.call('SoftLayer_User_Customer_Notification_Virtual_Guest', 'findByGuestId', vs_id) + + def add_notification(self, virtual_id, user_id): + """Create a user virtual notification entry""" + + template = {"guestId": virtual_id, "userId": user_id} + mask = 'user' + return self.client.call('SoftLayer_User_Customer_Notification_Virtual_Guest', + 'createObject', template, mask=mask) diff --git a/docs/cli/vs.rst b/docs/cli/vs.rst index c387d00a0..f8143f4e1 100644 --- a/docs/cli/vs.rst +++ b/docs/cli/vs.rst @@ -279,6 +279,14 @@ If no timezone is specified, IMS local time (CST) will be assumed, which might n :prog: virtual access :show-nested: +.. click:: SoftLayer.CLI.virt.notifications:cli + :prog: virtual notifications + :show-nested: + +.. click:: SoftLayer.CLI.virt.add_notification:cli + :prog: virtual add-notification + :show-nested: + Manages the migration of virutal guests. Supports migrating virtual guests on Dedicated Hosts as well. Reserved Capacity diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index e71067efe..46a0b994c 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -961,3 +961,11 @@ def test_last_transaction_empty(self): def test_user_access(self): result = self.run_command(['vs', 'access', '100']) self.assert_no_fail(result) + + def test_notifications(self): + result = self.run_command(['vs', 'notifications', '100']) + self.assert_no_fail(result) + + def test_add_notification(self): + result = self.run_command(['vs', 'add-notification', '100', '--users', '123456']) + self.assert_no_fail(result) diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index 18c94d7fb..8bf2fe8ef 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -1330,3 +1330,11 @@ def test_browser_access_log(self): result = self.vs.browser_access_log(1234) self.assertTrue(result) self.assert_called_with('SoftLayer_Virtual_Guest', 'getBrowserConsoleAccessLogs', identifier=1234) + + def test_notification(self): + self.vs.get_notifications(100) + self.assert_called_with('SoftLayer_User_Customer_Notification_Virtual_Guest', 'findByGuestId') + + def test_add_notification(self): + self.vs.add_notification(100, 123456) + self.assert_called_with('SoftLayer_User_Customer_Notification_Virtual_Guest', 'createObject') From 6877108ad7b1911adbaf63f0100a6b2165b6f403 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 27 Sep 2022 17:21:01 -0400 Subject: [PATCH 098/710] fix the team code review comments --- SoftLayer/CLI/hardware/add_notification.py | 4 ++-- SoftLayer/CLI/hardware/notifications.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/hardware/add_notification.py b/SoftLayer/CLI/hardware/add_notification.py index eda48d149..5a7ba9ba3 100644 --- a/SoftLayer/CLI/hardware/add_notification.py +++ b/SoftLayer/CLI/hardware/add_notification.py @@ -11,7 +11,7 @@ @click.command(cls=SoftLayer.CLI.command.SLCommand, ) @click.argument('identifier') @click.option('--users', multiple=True, - help='An IP address to authorize ') + help='UserId to be notified on monitoring failure.') @environment.pass_env def cli(env, identifier, users): """Create a user hardware notification entry.""" @@ -26,6 +26,6 @@ def cli(env, identifier, users): notification = hardware.add_notification(identifier, user) table.add_row([notification['id'], notification['hardware']['fullyQualifiedDomainName'], notification['user']['username'], notification['user']['email'], - notification['user']['lastName'], notification['user']['firstName']]) + notification['user']['firstName'], notification['user']['lastName']]) env.fout(table) diff --git a/SoftLayer/CLI/hardware/notifications.py b/SoftLayer/CLI/hardware/notifications.py index abb1273a9..76c2bee6d 100644 --- a/SoftLayer/CLI/hardware/notifications.py +++ b/SoftLayer/CLI/hardware/notifications.py @@ -25,6 +25,6 @@ def cli(env, identifier): for notification in notifications: table.add_row([notification['hardware']['fullyQualifiedDomainName'], notification['hardware']['hostname'], notification['user']['username'], notification['user']['email'], - notification['user']['lastName'], notification['user']['firstName']]) + notification['user']['firstName'], notification['user']['lastName']]) env.fout(table) From 34ba3ae69e63bec7226a341c9227460aba8a7394 Mon Sep 17 00:00:00 2001 From: edsonarios Date: Fri, 30 Sep 2022 12:35:45 -0400 Subject: [PATCH 099/710] Change regex in rich text in simple option in help text --- SoftLayer/CLI/command.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/command.py b/SoftLayer/CLI/command.py index 3a93b57f8..95013769a 100644 --- a/SoftLayer/CLI/command.py +++ b/SoftLayer/CLI/command.py @@ -23,7 +23,7 @@ class OptionHighlighter(RegexHighlighter): """Provides highlighter regex for the Command help""" highlights = [ - r"(?P\-\w)", # single options like -v + r"(?P^\-\w)", # single options like -v r"(?P