pax_global_header00006660000000000000000000000064122260034450014510gustar00rootroot0000000000000052 comment=284df5a380bd57ae8c5b6dfdd4aaa08e94398fab softlayer-api-python-client-3.0.1/000077500000000000000000000000001222600344500170635ustar00rootroot00000000000000softlayer-api-python-client-3.0.1/.gitignore000066400000000000000000000001501222600344500210470ustar00rootroot00000000000000.DS_Store *.swp Thumbs.db .svn ._* *.pyc .coverage cover/* .tox docs/_build/* build/* dist/* *.egg-info softlayer-api-python-client-3.0.1/.travis.yml000066400000000000000000000004761222600344500212030ustar00rootroot00000000000000language: python python: - "2.6" - "2.7" - "3.2" - "3.3" - "pypy" # command to install dependencies install: - "pip install -r requirements.txt --use-mirrors" - "if [[ $TRAVIS_PYTHON_VERSION = 2.6 ]]; then pip install unittest2 --use-mirrors; fi" # command to run tests script: python setup.py nosetests softlayer-api-python-client-3.0.1/CHANGELOG000066400000000000000000000065771222600344500203140ustar00rootroot000000000000003.0.1 * CLI: Fixed an error message about pricing information that appeared when ordering a new private subnet. * CLI+API: Added ability to specify SSH keys when reloading CCIs and servers. 3.0.0 * Many bug fixes and consistency improvements * API: Removes old API client interfaces which have been deprecated in the v2. See link for more details: https://softlayer-api-python-client.readthedocs.org/en/latest/api/client/#backwards-compatibility * CLI+API: Improved dedicated server ordering. Adds power management for hardware servers: power-on, power-off, power-cycle, reboot * CLI+API: Adds a networking manager and adds several network-related CLI modules. This includes the ability to: * list, create, cancel and assign global IPs * list, create, cancel and detail subnets. Also has the ability to lookup details about an IP address with 'sl subnet lookup' * list, detail VLANs * show and edit RWhois data * CLI+API: Adds SoftLayer Message Queue Service bindings (as a manager) and a CLI counterpart. With this you can interact with existing message queue accounts * CLI+API: Ability to manage SSH Keys with a manager and a CLI module * CLI+API: Adds the ability to create CCIs with the following options: metadata, post-install script, SSH key * CLI: Adds templating for creating CCIs and hardware nodes which can be used to create more CCIs and hardware with the same settings * CLI+API: Adds the ability to create hardware servers with a default SSH key * CLI: Adds a --debug option to print out debugging information. --debug=3 is the highest log level which prints full HTTP request/responses including the body * CLI: The commands in the main help are now organized into categories 2.3.0 * Several bug fixes and improvements * Removed Python 2.5 support. Some stuff MIGHT work with 2.5 but it is no longer tested * API: Refactored managers into their own module to not clutter the top level * CLI+API: Added much more hardware support: Filters for hardware listing, dedicated server/bare metal cloud ordering, hardware cancellation * CLI+API: Added DNS Zone filtering (server side) * CLI+API: Added Post Install script support for CCIs and hardware * CLI: Added Message queue functionality * CLI: Added --debug option to CLI commands * API: Added more logging * API: Added token-based auth so you can use the API bindings with your username/password if you want. (It's still highly recommended to use your API key instead of your password) 2.2.0 * Consistency changes/bug fixes * Added sphinx documentation. See it here: https://softlayer-api-python-client.readthedocs.org * CCI: Adds Support for Additional Disks * CCI: Adds a way to block until transactions are done on a CCI * CLI: For most CCI commands, you can specify id, hostname, private ip or public ip as * CLI: Adds the ability to filter list results for CCIs * API: for large result sets, requests can now be chunked into smaller batches on the server side. Using service.iter_call('getObjects', ...) or service.getObjects(..., iter=True) will return a generator regardless of the results returned. offset and limit can be passed in like normal. An additional named parameter of 'chunk' is used to limit the number of items coming back in a single request, defaults to 100 softlayer-api-python-client-3.0.1/LICENSE000066400000000000000000000021041222600344500200650ustar00rootroot00000000000000Copyright (c) 2013 SoftLayer Technologies, Inc. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.softlayer-api-python-client-3.0.1/MANIFEST.in000066400000000000000000000000431222600344500206160ustar00rootroot00000000000000include LICENSE include README.rst softlayer-api-python-client-3.0.1/README.rst000066400000000000000000000036051222600344500205560ustar00rootroot00000000000000SoftLayer API Python Client =========================== .. image:: https://api.travis-ci.org/softlayer/softlayer-api-python-client.png :target: https://travis-ci.org/softlayer/softlayer-api-python-client .. image:: https://badge.fury.io/py/SoftLayer.png :target: http://badge.fury.io/py/SoftLayer .. image:: https://pypip.in/d/SoftLayer/badge.png :target: https://crate.io/packages/SoftLayer SoftLayer API bindings for Python. For use with `SoftLayer's API `_. This library provides a simple interface to interact with SoftLayer's XML-RPC API and provides support for many of SoftLayer API's features like `object masks `_ and a command-line interface that can be used to access various SoftLayer services using the API. Documentation ------------- Documentation is available at https://softlayer-api-python-client.readthedocs.org/ Installation ------------ Install via pip: .. code-block:: bash $ pip install softlayer Or you can install from source. Download source and run: .. code-block:: bash $ python setup.py install The most up to date version of this library can be found on the SoftLayer GitHub public repositories: http://github.com/softlayer. Please post to the SoftLayer forums http://forums.softlayer.com/ or open a support ticket in the SoftLayer customer portal if you have any questions regarding use of this library. System Requirements ------------------- * This library has been tested on Python 2.6, 2.7, 3.2 and 3.3. * A valid SoftLayer API username and key are required to call SoftLayer's API * A connection to SoftLayer's private network is required to connect to SoftLayer’s private network API endpoints. Copyright --------- This software is Copyright (c) 2013 SoftLayer Technologies, Inc. See the bundled LICENSE file for more information. softlayer-api-python-client-3.0.1/SoftLayer/000077500000000000000000000000001222600344500207735ustar00rootroot00000000000000softlayer-api-python-client-3.0.1/SoftLayer/API.py000066400000000000000000000274431222600344500217700ustar00rootroot00000000000000""" SoftLayer.API ~~~~~~~~~~~~~ SoftLayer API bindings :copyright: (c) 2013, SoftLayer Technologies, Inc. All rights reserved. :license: MIT, see LICENSE for more details. """ import datetime from consts import API_PUBLIC_ENDPOINT, API_PRIVATE_ENDPOINT, USER_AGENT from transports import make_xml_rpc_api_call from auth import TokenAuthentication from config import get_client_settings __all__ = ['Client', 'TimedClient', 'API_PUBLIC_ENDPOINT', 'API_PRIVATE_ENDPOINT'] VALID_CALL_ARGS = set([ 'id', 'mask', 'filter', 'headers', 'raw_headers', 'limit', 'offset', ]) class Client(object): """ A SoftLayer API client. :param username: an optional API username if you wish to bypass the package's built-in username :param api_key: an optional API key if you wish to bypass the package's built in API key :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 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 Usage: >>> import SoftLayer >>> client = SoftLayer.Client(username="username", api_key="api_key") >>> resp = client['Account'].getObject() >>> resp['companyName'] 'Your Company' """ _prefix = "SoftLayer_" def __init__(self, username=None, api_key=None, endpoint_url=None, timeout=None, auth=None, config_file=None): settings = get_client_settings(username=username, api_key=api_key, endpoint_url=endpoint_url, timeout=timeout, auth=auth, config_file=config_file) self.auth = settings.get('auth') self.endpoint_url = ( settings.get('endpoint_url') or API_PUBLIC_ENDPOINT).rstrip('/') self.timeout = None self.last_calls = [] if settings.get('timeout'): self.timeout = float(settings.get('timeout')) def authenticate_with_password(self, username, password, security_question_id=None, security_question_answer=None): """ Performs Username/Password Authentication and gives back an auth handler to use to create a client that uses token-based auth. :param string username: your SoftLayer username :param string password: your SoftLayer password :param int security_question_id: The security question id to answer :param string security_question_answer: The answer to the security question """ self.auth = None res = self['User_Customer'].getPortalLoginToken( username, password, security_question_id, security_question_answer) self.auth = TokenAuthentication(res['userId'], res['hash']) return (res['userId'], res['hash']) def __getitem__(self, name): """ Get a SoftLayer Service. :param name: The name of the service. E.G. Account Usage: >>> import SoftLayer >>> client = SoftLayer.Client() >>> client['Account'] """ return Service(self, name) def call(self, service, method, *args, **kwargs): """ Make a SoftLayer API call :param service: the name of the SoftLayer API service :param method: the method to call on the service :param \*args: same optional arguments that ``Service.call`` takes :param \*\*kwargs: same optional keyword arguments that ``Service.call`` takes :param service: the name of the SoftLayer API service Usage: >>> import SoftLayer >>> client = SoftLayer.Client() >>> client['Account'].getVirtualGuests(mask="id", limit=10) [...] """ if kwargs.pop('iter', False): return self.iter_call(service, method, *args, **kwargs) invalid_kwargs = set(kwargs.keys()) - VALID_CALL_ARGS if invalid_kwargs: raise TypeError( 'Invalid keyword arguments: %s' % ','.join(invalid_kwargs)) if not service.startswith(self._prefix): service = self._prefix + service objectid = kwargs.get('id') objectmask = kwargs.get('mask') objectfilter = kwargs.get('filter') headers = kwargs.get('headers', {}) raw_headers = kwargs.get('raw_headers') limit = kwargs.get('limit') offset = kwargs.get('offset', 0) if self.auth: headers.update(self.auth.get_headers()) if objectid is not None: headers[service + 'InitParameters'] = {'id': objectid} if objectmask is not None: headers.update(self.__format_object_mask(objectmask, service)) if objectfilter is not None: headers['%sObjectFilter' % service] = objectfilter if limit: headers['resultLimit'] = { 'limit': limit, 'offset': offset, } http_headers = { 'User-Agent': USER_AGENT, 'Content-Type': 'application/xml', } if raw_headers: http_headers.update(raw_headers) uri = '/'.join([self.endpoint_url, service]) return make_xml_rpc_api_call(uri, method, args, headers=headers, http_headers=http_headers, timeout=self.timeout) __call__ = call def iter_call(self, service, method, chunk=100, limit=None, offset=0, *args, **kwargs): """ A generator that deals with paginating through results. :param service: the name of the SoftLayer API service :param method: the method to call on the service :param integer chunk: result size for each API call :param \*args: same optional arguments that ``Service.call`` takes :param \*\*kwargs: same optional keyword arguments that ``Service.call`` takes """ if chunk <= 0: raise AttributeError("Chunk size should be greater than zero.") if limit: chunk = min(chunk, limit) result_count = 0 kwargs['iter'] = False while True: if limit: # We've reached the end of the results if result_count >= limit: break # Don't over-fetch past the given limit if chunk + result_count > limit: chunk = limit - result_count results = self.call(service, method, offset=offset, limit=chunk, *args, **kwargs) # It looks like we ran out results if not results: break # Apparently this method doesn't return a list. # Why are you even iterating over this? if not isinstance(results, list): yield results break for item in results: yield item result_count += 1 offset += chunk if len(results) < chunk: break def __format_object_mask(self, objectmask, service): """ Format new and old style object masks into proper headers. :param objectmask: a string- or dict-based object mask :param service: a SoftLayer API service name """ if isinstance(objectmask, dict): mheader = '%sObjectMask' % service else: mheader = self._prefix + 'ObjectMask' objectmask = objectmask.strip() if not objectmask.startswith('mask') \ and not objectmask.startswith('['): objectmask = "mask[%s]" % objectmask return {mheader: {'mask': objectmask}} def __repr__(self): return "" \ % (self.endpoint_url, self.auth) __str__ = __repr__ class TimedClient(Client): """ Subclass of Client() Using this class will time every call to the API and store it in an internal list. This will have a slight impact on your client's memory usage and performance. You should only use this for debugging. """ last_calls = [] def call(self, service, method, *args, **kwargs): """ See Client.call for documentation. """ start_time = datetime.datetime.utcnow() result = super(TimedClient, self).call(service, method, *args, **kwargs) end_time = datetime.datetime.utcnow() diff = end_time - start_time self.last_calls.append((service + '.' + method, start_time.strftime('%s'), diff.total_seconds())) return result def get_last_calls(self): """ Retrieves the last_calls property. This property will contain a list of tuples in the form ('SERVICE.METHOD', initiated_utc_timestamp, execution_time) """ last_calls = self.last_calls self.last_calls = [] return last_calls class Service(object): def __init__(self, client, name): self.client = client self.name = name def call(self, name, *args, **kwargs): """ Make a SoftLayer API call :param method: the method to call on the service :param \*args: (optional) arguments for the remote call :param id: (optional) id for the resource :param mask: (optional) object mask :param dict filter: (optional) filter dict :param dict headers: (optional) optional XML-RPC headers :param dict raw_headers: (optional) HTTP transport headers :param int limit: (optional) return at most this many results :param int offset: (optional) offset results by this many :param boolean iter: (optional) if True, returns a generator with the results Usage: >>> import SoftLayer >>> client = SoftLayer.Client() >>> client['Account'].getVirtualGuests(mask="id", limit=10) [...] """ return self.client.call(self.name, name, *args, **kwargs) __call__ = call def iter_call(self, name, *args, **kwargs): """ A generator that deals with paginating through results. :param method: the method to call on the service :param integer chunk: result size for each API call :param \*args: same optional arguments that ``Service.call`` takes :param \*\*kwargs: same optional keyword arguments that ``Service.call`` takes Usage: >>> import SoftLayer >>> client = SoftLayer.Client() >>> gen = client['Account'].getVirtualGuests(iter=True) >>> for virtual_guest in gen: ... virtual_guest['id'] ... 1234 4321 """ return self.client.iter_call(self.name, name, *args, **kwargs) def __getattr__(self, name): if name in ["__name__", "__bases__"]: raise AttributeError("'Obj' object has no attribute '%s'" % name) def call_handler(*args, **kwargs): return self(name, *args, **kwargs) return call_handler def __repr__(self): return "" % (self.name,) __str__ = __repr__ softlayer-api-python-client-3.0.1/SoftLayer/CLI/000077500000000000000000000000001222600344500214025ustar00rootroot00000000000000softlayer-api-python-client-3.0.1/SoftLayer/CLI/__init__.py000066400000000000000000000004561222600344500235200ustar00rootroot00000000000000""" SoftLayer.CLI ~~~~~~~~~~~~~~ Contains all code related to the CLI interface :copyright: (c) 2013, SoftLayer Technologies, Inc. All rights reserved. :license: MIT, see LICENSE for more details. """ import SoftLayer.CLI.core # NOQA from SoftLayer.CLI.helpers import * # NOQA softlayer-api-python-client-3.0.1/SoftLayer/CLI/core.py000066400000000000000000000161151222600344500227100ustar00rootroot00000000000000""" usage: sl [...] sl help sl help sl [-h | --help] SoftLayer Command-line Client The available modules are: Compute: bmc Bare Metal Cloud cci Cloud Compute Instances image Manages compute and flex images metadata Get details about this machine. Also available with 'my' and 'meta' server Hardware servers sshkey Manage SSH keys on your account Networking: dns Domain Name System firewall Firewall rule and security management globalip Global IP address management rwhois RWhoIs operations ssl Manages SSL subnet Subnet ordering and management vlan Manage VLANs on your account Storage: iscsi View iSCSI details nas View NAS details General: config View and edit configuration for this tool summary Display an overall summary of your account help Show help See 'sl help ' for more information on a specific module. To use most commands your SoftLayer username and api_key need to be configured. The easiest way to do that is to use: 'sl config setup' """ # :copyright: (c) 2013, SoftLayer Technologies, Inc. All rights reserved. # :license: MIT, see LICENSE for more details. import sys import logging from docopt import docopt, DocoptExit from SoftLayer import Client, TimedClient, SoftLayerError, SoftLayerAPIError from SoftLayer.consts import VERSION from helpers import CLIAbort, ArgumentError, format_output, KeyValueTable from environment import ( Environment, CLIRunnableType, InvalidCommand, InvalidModule) DEBUG_LOGGING_MAP = { '0': logging.CRITICAL, '1': logging.WARNING, '2': logging.INFO, '3': logging.DEBUG } VALID_FORMATS = ['raw', 'table', 'json'] class CommandParser(object): def __init__(self, env): self.env = env def get_main_help(self): return __doc__.strip() def get_module_help(self, module_name): module = self.env.load_module(module_name) arg_doc = module.__doc__ return arg_doc.strip() def get_command_help(self, module_name, command_name): command = self.env.get_command(module_name, command_name) default_format = 'raw' if sys.stdout.isatty(): default_format = 'table' arg_doc = command.__doc__ if 'confirm' in command.options: arg_doc += """ Prompt Options: -y, --really Confirm all prompt actions """ if '[options]' in arg_doc: arg_doc += """ Standard Options: --format=ARG Output format. [Options: table, raw] [Default: %s] -C FILE --config=FILE Config file location. [Default: ~/.softlayer] --debug=LEVEL Specifies the debug noise level 1=warn, 2=info, 3=debug --timings Time each API call and display after results -h --help Show this screen """ % default_format return arg_doc.strip() def parse_main_args(self, args): main_help = self.get_main_help() arguments = docopt( main_help, version=VERSION, argv=args, options_first=True) arguments[''] = self.env.get_module_name(arguments['']) return arguments def parse_module_args(self, module_name, args): arg_doc = self.get_module_help(module_name) arguments = docopt( arg_doc, version=VERSION, argv=[module_name] + args, options_first=True) module = self.env.load_module(module_name) return module, arguments def parse_command_args(self, module_name, command_name, args): command = self.env.get_command(module_name, command_name) arg_doc = self.get_command_help(module_name, command_name) arguments = docopt(arg_doc, version=VERSION, argv=[module_name] + args) return command, arguments def parse(self, args): # handle `sl ...` main_args = self.parse_main_args(args) module_name = main_args[''] # handle `sl ...` module, module_args = self.parse_module_args( module_name, main_args['']) # get the command argument command_name = module_args.get('') # handle `sl ...` return self.parse_command_args( module_name, command_name, main_args['']) def main(args=sys.argv[1:], env=Environment()): """ Entry point for the command-line client. """ # Parse Top-Level Arguments CLIRunnableType.env = env exit_status = 0 resolver = CommandParser(env) try: command, command_args = resolver.parse(args) # Set logging level debug_level = command_args.get('--debug') if debug_level: logger = logging.getLogger() h = logging.StreamHandler() logger.addHandler(h) logger.setLevel(DEBUG_LOGGING_MAP.get(debug_level, logging.DEBUG)) if command_args.get('--timings'): client = TimedClient(config_file=command_args.get('--config')) else: client = Client(config_file=command_args.get('--config')) # Do the thing data = command.execute(client, command_args) if data: format = command_args.get('--format', 'table') if format not in VALID_FORMATS: raise ArgumentError('Invalid format "%s"' % format) s = format_output(data, fmt=format) if s: env.out(s) if command_args.get('--timings'): format = command_args.get('--format', 'table') api_calls = client.get_last_calls() t = KeyValueTable(['call', 'time']) for call, initiated, duration in api_calls: t.add_row([call, duration]) env.err(format_output(t, fmt=format)) except InvalidCommand as e: env.err(resolver.get_module_help(e.module_name)) if e.command_name: env.err('') env.err(str(e)) exit_status = 1 except InvalidModule as e: env.err(resolver.get_main_help()) if e.module_name: env.err('') env.err(str(e)) exit_status = 1 except DocoptExit as e: env.err(e.usage) env.err( '\nUnknown argument(s), use -h or --help for available options') exit_status = 127 except KeyboardInterrupt: env.out('') exit_status = 1 except CLIAbort as e: env.err(str(e.message)) exit_status = e.code except SystemExit as e: exit_status = e.code except SoftLayerAPIError as e: if 'invalid api token' in e.faultString.lower(): env.out("Authentication Failed: To update your credentials, use " "'sl config setup'") else: env.err(str(e)) exit_status = 1 except SoftLayerError as e: env.err(str(e)) exit_status = 1 except Exception as e: import traceback env.err(traceback.format_exc()) exit_status = 1 sys.exit(exit_status) softlayer-api-python-client-3.0.1/SoftLayer/CLI/environment.py000066400000000000000000000063061222600344500243250ustar00rootroot00000000000000""" SoftLayer.CLI.environment ~~~~~~~~~~~~~~~~~~~~~~~~~ Abstracts everything related to the user's environment when running the CLI :copyright: (c) 2013, SoftLayer Technologies, Inc. All rights reserved. :license: MIT, see LICENSE for more details. """ import sys import getpass from importlib import import_module import os import os.path from SoftLayer.CLI.modules import get_module_list from SoftLayer import SoftLayerError class InvalidCommand(SoftLayerError): " Raised when trying to use a command that does not exist " def __init__(self, module_name, command_name, *args): self.module_name = module_name self.command_name = command_name error = 'Invalid command: "%s".' % self.command_name SoftLayerError.__init__(self, error, *args) class InvalidModule(SoftLayerError): " Raised when trying to use a module that does not exist " def __init__(self, module_name, *args): self.module_name = module_name error = 'Invalid module: "%s".' % self.module_name SoftLayerError.__init__(self, error, *args) class Environment(object): # {'module_name': {'action': 'actionClass'}} plugins = {} aliases = { 'meta': 'metadata', 'my': 'metadata', 'vm': 'cci', 'hardware': 'server', 'hw': 'server', 'bmetal': 'bmc', } stdout = sys.stdout stderr = sys.stderr def get_command(self, module_name, command_name): actions = self.plugins.get(module_name) or {} if command_name in actions: return actions[command_name] if None in actions: return actions[None] raise InvalidCommand(module_name, command_name) def get_module_name(self, module_name): if module_name in self.aliases: return self.aliases[module_name] return module_name def load_module(self, module_name): # pragma: no cover try: return import_module('SoftLayer.CLI.modules.%s' % module_name) except ImportError: raise InvalidModule(module_name) def add_plugin(self, cls): command = cls.__module__.split('.')[-1] if command not in self.plugins: self.plugins[command] = {} self.plugins[command][cls.action] = cls def plugin_list(self): return get_module_list() def out(self, s, nl=True): self.stdout.write(s) if nl: self.stdout.write(os.linesep) def err(self, s, nl=True): self.stderr.write(s) if nl: self.stderr.write(os.linesep) def input(self, prompt): return raw_input(prompt) def getpass(self, prompt): return getpass.getpass(prompt) def exit(self, code=0): sys.exit(code) class CLIRunnableType(type): env = Environment() def __init__(cls, name, bases, attrs): super(CLIRunnableType, cls).__init__(name, bases, attrs) if cls.env and name != 'CLIRunnable': cls.env.add_plugin(cls) class CLIRunnable(object): __metaclass__ = CLIRunnableType options = [] action = None @staticmethod def add_additional_args(parser): pass @staticmethod def execute(client, args): pass softlayer-api-python-client-3.0.1/SoftLayer/CLI/exceptions.py000066400000000000000000000012651222600344500241410ustar00rootroot00000000000000""" SoftLayer.CLI.exceptions ~~~~~~~~~~~~~~~~~~~~~~~~ Exceptions to be used in the CLI modules. :copyright: (c) 2013, SoftLayer Technologies, Inc. All rights reserved. :license: MIT, see LICENSE for more details. """ class CLIHalt(SystemExit): def __init__(self, code=0, *args): super(CLIHalt, self).__init__(*args) self.code = code class CLIAbort(CLIHalt): def __init__(self, msg, *args): super(CLIAbort, self).__init__(code=2, *args) self.message = msg class ArgumentError(CLIAbort): def __init__(self, msg, *args): super(CLIAbort, self).__init__(code=2, *args) self.message = "Argument Error: %s" % msg softlayer-api-python-client-3.0.1/SoftLayer/CLI/formatting.py000066400000000000000000000154751222600344500241420ustar00rootroot00000000000000""" SoftLayer.formatting ~~~~~~~~~~~~~~~~~~~~ Provider classes and helper functions to display output onto a command-line. :copyright: (c) 2013, SoftLayer Technologies, Inc. All rights reserved. :license: MIT, see LICENSE for more details. """ import os import json from prettytable import PrettyTable, FRAME, NONE def format_output(data, fmt='table'): """ Given some data, will format it for output :param data: One of: String, Table, FormattedItem, List, Tuple, SequentialOutput :param string fmt (optional): One of: table, raw, json, python """ if isinstance(data, basestring): if fmt == 'json': return json.dumps(data) return data # responds to .prettytable() if hasattr(data, 'prettytable'): if fmt == 'table': return str(format_prettytable(data)) elif fmt == 'raw': return str(format_no_tty(data)) # responds to .to_python() if hasattr(data, 'to_python'): if fmt == 'json': return json.dumps( format_output(data, fmt='python'), indent=4, cls=CLIJSONEncoder) elif fmt == 'python': return data.to_python() # responds to .formatted if hasattr(data, 'formatted'): if fmt == 'table': return str(data.formatted) # responds to .separator if hasattr(data, 'separator'): output = [format_output(d, fmt=fmt) for d in data if d] return str(SequentialOutput(data.separator, output)) # is iterable if isinstance(data, list) or isinstance(data, tuple): output = [format_output(d, fmt=fmt) for d in data] if fmt == 'python': return output return format_output(listing(output, separator=os.linesep)) # fallback, convert this odd object to a string return data def format_prettytable(table): for i, row in enumerate(table.rows): for j, item in enumerate(row): table.rows[i][j] = format_output(item) t = table.prettytable() t.hrules = FRAME t.horizontal_char = '.' t.vertical_char = ':' t.junction_char = ':' return t def format_no_tty(table): for i, row in enumerate(table.rows): for j, item in enumerate(row): table.rows[i][j] = format_output(item, fmt='raw') t = table.prettytable() for col in table.columns: t.align[col] = 'l' t.hrules = NONE t.border = False t.header = False t.left_padding_width = 0 t.right_padding_width = 2 return t def mb_to_gb(megabytes): """ Takes in the number of megabytes and returns a FormattedItem that displays gigabytes. :param int megabytes: number of megabytes """ return FormattedItem(megabytes, "%dG" % (float(megabytes) / 1024)) def gb(gigabytes): """ Takes in the number of gigabytes and returns a FormattedItem that displays gigabytes. :param int gigabytes: number of gigabytes """ return FormattedItem(int(float(gigabytes)) * 1024, "%dG" % int(float(gigabytes))) def blank(): """ Returns FormatedItem to make pretty output use a dash and raw formatting to use NULL """ return FormattedItem(None, '-') def listing(items, separator=','): """ Given an iterable, returns a FormatedItem which display a list of items :param items: An iterable that outputs strings :param string separator: the separator to use """ return SequentialOutput(separator, items) def active_txn(item): """ Returns a FormattedItem describing the active transaction (if any) on the given object. If no active transaction is running, returns a blank FormattedItem. :param item: An object capable of having an active transaction """ if not item['activeTransaction']['transactionStatus']: return blank() return FormattedItem( item['activeTransaction']['transactionStatus'].get('name'), item['activeTransaction']['transactionStatus'].get('friendlyName')) def valid_response(prompt, *valid): ans = raw_input(prompt).lower() if ans in valid: return True elif ans == '': return None return False def confirm(prompt_str, default=False): if default: prompt = '%s [Y/n]: ' % prompt_str else: prompt = '%s [y/N]: ' % prompt_str response = valid_response(prompt, 'y', 'yes', 'yeah', 'yup', 'yolo') if response is None: return default return response def no_going_back(confirmation): if not confirmation: confirmation = 'yes' return valid_response( 'This action cannot be undone! ' 'Type "%s" or press Enter to abort: ' % confirmation, str(confirmation)) class SequentialOutput(list): def __init__(self, separator=os.linesep, *args, **kwargs): self.separator = separator super(SequentialOutput, self).__init__(*args, **kwargs) def to_python(self): return self def __str__(self): return self.separator.join(str(x) for x in self) class CLIJSONEncoder(json.JSONEncoder): def default(self, obj): if hasattr(obj, 'to_python'): return obj.to_python() return super(CLIJSONEncoder, self).default(obj) class Table(object): def __init__(self, columns): self.columns = columns self.rows = [] self.align = {} self.format = {} self.sortby = None def add_row(self, row): self.rows.append(row) def _format_python_value(self, value): if hasattr(value, 'to_python'): return value.to_python() return value def to_python(self): # Adding rows l = [] for row in self.rows: formatted_row = [self._format_python_value(v) for v in row] l.append(dict(zip(self.columns, formatted_row))) return l def prettytable(self): """ Returns a new prettytable instance. """ t = PrettyTable(self.columns) if self.sortby: t.sortby = self.sortby for a_col, alignment in self.align.items(): t.align[a_col] = alignment # Adding rows for row in self.rows: t.add_row(row) return t class KeyValueTable(Table): def to_python(self): d = {} for row in self.rows: d[row[0]] = self._format_python_value(row[1]) return d class FormattedItem(object): def __init__(self, original, formatted=None): self.original = original if formatted is not None: self.formatted = formatted else: self.formatted = self.original def to_python(self): return self.original def __str__(self): if self.original is None: return 'NULL' return str(self.original) __repr__ = __str__ softlayer-api-python-client-3.0.1/SoftLayer/CLI/helpers.py000066400000000000000000000035141222600344500234210ustar00rootroot00000000000000""" SoftLayer.CLI.helpers ~~~~~~~~~~~~~~~~~~~~~ Helpers to be used in CLI modules in SoftLayer.CLI.modules.* :copyright: (c) 2013, SoftLayer Technologies, Inc. All rights reserved. :license: MIT, see LICENSE for more details. """ from SoftLayer.utils import NestedDict from SoftLayer.CLI.environment import CLIRunnable from exceptions import CLIHalt, CLIAbort, ArgumentError from formatting import ( Table, KeyValueTable, FormattedItem, SequentialOutput, confirm, no_going_back, mb_to_gb, gb, listing, blank, format_output, active_txn, valid_response) from template import update_with_template_args, export_to_template __all__ = [ # Core/Misc 'CLIRunnable', 'NestedDict', 'FALSE_VALUES', 'resolve_id', # Exceptions 'CLIAbort', 'CLIHalt', 'ArgumentError', # Formatting 'Table', 'KeyValueTable', 'FormattedItem', 'SequentialOutput', 'valid_response', 'confirm', 'no_going_back', 'mb_to_gb', 'gb', 'listing', 'format_output', 'blank', 'active_txn', # Template 'update_with_template_args', 'export_to_template', ] FALSE_VALUES = ['0', 'false', 'FALSE', 'no', 'False'] def resolve_id(resolver, identifier, name='object'): """ Resolves a single id using an id resolver function which returns a list of ids. :param resolver: function that resolves ids. Should return None or a list of ids. :param string identifier: a string identifier used to resolve ids :param string name: the object type, to be used in error messages """ ids = resolver(identifier) if len(ids) == 0: raise CLIAbort("Error: Unable to find %s '%s'" % (name, identifier)) if len(ids) > 1: raise CLIAbort( "Error: Multiple %s found for '%s': %s" % (name, identifier, ', '.join([str(_id) for _id in ids]))) return ids[0] softlayer-api-python-client-3.0.1/SoftLayer/CLI/modules/000077500000000000000000000000001222600344500230525ustar00rootroot00000000000000softlayer-api-python-client-3.0.1/SoftLayer/CLI/modules/__init__.py000066400000000000000000000005771222600344500251740ustar00rootroot00000000000000""" SoftLayer.CLI.modules ~~~~~~~~~~~~~~~~~~~~~ Contains all plugable modules for the CLI interface :copyright: (c) 2013, SoftLayer Technologies, Inc. All rights reserved. :license: MIT, see LICENSE for more details. """ from pkgutil import iter_modules def get_module_list(): actions = [action[1] for action in iter_modules(__path__)] return actions softlayer-api-python-client-3.0.1/SoftLayer/CLI/modules/bmc.py000066400000000000000000000445661222600344500242040ustar00rootroot00000000000000""" usage: sl bmc [] [...] [options] sl bmc [-h | --help] Manage bare metal instances The available commands are: cancel Cancels a bare metal instance create Create a new bare metal instance create-options Output available available options when creating a server For several commands, will be asked for. This can be the id, hostname or the ip address for a piece of hardware. """ # :copyright: (c) 2013, SoftLayer Technologies, Inc. All rights reserved. # :license: MIT, see LICENSE for more details. import re from os import linesep from SoftLayer.CLI import ( CLIRunnable, Table, KeyValueTable, no_going_back, confirm, listing, FormattedItem) from SoftLayer.CLI.helpers import ( ArgumentError, CLIAbort, SequentialOutput, update_with_template_args, FALSE_VALUES, resolve_id) from SoftLayer import HardwareManager, SshKeyManager class BMCCreateOptions(CLIRunnable): """ usage: sl bmc create-options [options] Output available available options when creating a bare metal instance. Options: --all Show all options. default if no other option provided --cpu Show CPU options --datacenter Show datacenter options --disk Show disk options --memory Show memory size options --nic Show NIC speed options --os Show operating system options """ action = 'create-options' options = ['datacenter', 'cpu', 'memory', 'os', 'disk', 'nic'] @classmethod def execute(cls, client, args): t = KeyValueTable(['Name', 'Value']) t.align['Name'] = 'r' t.align['Value'] = 'l' show_all = True for opt_name in cls.options: if args.get("--" + opt_name): show_all = False break mgr = HardwareManager(client) bmi_options = mgr.get_bare_metal_create_options() if args['--all']: show_all = True if args['--datacenter'] or show_all: results = cls.get_create_options(bmi_options, 'datacenter')[0] t.add_row([results[0], listing(sorted(results[1]))]) if args['--cpu'] or args['--memory'] or show_all: results = cls.get_create_options(bmi_options, 'cpu') memory_cpu_table = Table(['memory', 'cpu']) for result in results: memory_cpu_table.add_row([ result[0], listing( [item[0] for item in sorted( result[1], key=lambda x: int(x[0]) )])]) t.add_row(['memory/cpu', memory_cpu_table]) if args['--os'] or show_all: results = cls.get_create_options(bmi_options, 'os') for result in results: t.add_row([ result[0], listing( [item[0] for item in sorted(result[1])], separator=linesep )]) if args['--disk'] or show_all: results = cls.get_create_options(bmi_options, 'disk')[0] t.add_row([results[0], listing( [item[0] for item in sorted(results[1])])]) if args['--nic'] or show_all: results = cls.get_create_options(bmi_options, 'nic') for result in results: t.add_row([result[0], listing( [item[0] for item in sorted(result[1],)])]) return t @classmethod def get_create_options(cls, bmi_options, section, pretty=True): """ This method can be used to parse the bare metal instance creation options into different sections. This can be useful for data validation as well as printing the options on a help screen. :param dict bmi_options: The instance options to parse. Must come from the .get_bare_metal_create_options() function in the HardwareManager. :param string section: The section to parse out. :param bool pretty: If true, it will return the results in a 'pretty' format that's easier to print. """ if 'datacenter' == section: datacenters = [loc['keyname'] for loc in bmi_options['locations']] return [('datacenter', datacenters)] elif 'cpu' == section or 'memory' == section: mem_options = {} cpu_regex = re.compile('(\d+) x ') memory_regex = re.compile(' - (\d+) GB Ram', re.I) for item in bmi_options['categories']['server_core']['items']: cpu = cpu_regex.search(item['description']).group(1) memory = memory_regex.search(item['description']).group(1) if cpu and memory: if memory not in mem_options: mem_options[memory] = [] mem_options[memory].append((cpu, item['price_id'])) results = [] for memory in sorted(mem_options.keys(), key=int): key = memory if pretty: key = memory results.append((key, mem_options[memory])) return results elif 'os' == section: os_regex = re.compile('(^[A-Za-z\s\/]+) ([\d\.]+)') bit_regex = re.compile(' \((\d+)\s*bit') extra_regex = re.compile(' - (.+)\(') # Encapsulate the code for generating the operating system code def _generate_os_code(name, version, bits, extra_info): name = name.replace(' Linux', '') name = name.replace('Enterprise', '') name = name.replace('GNU/Linux', '') os_code = name.strip().replace(' ', '_').upper() if os_code == 'RED_HAT': os_code = 'REDHAT' if 'UBUNTU' in os_code: version = re.sub('\.\d+', '', version) os_code += '_' + version.replace('.0', '') if bits: os_code += '_' + bits if extra_info: os_code += '_' + extra_info.strip() \ .replace(' Install', '').upper() return os_code # Also separate out the code for generating the Windows OS code # since it's significantly different from the rest. def _generate_windows_code(description): version_check = re.search('Windows Server (\d+)', description) version = version_check.group(1) os_code = 'WIN_' + version if 'Datacenter' in description: os_code += '-DC' elif 'Enterprise' in description: os_code += '-ENT' else: os_code += '-STD' if 'ith R2' in description: os_code += '-R2' elif 'ith Hyper-V' in description: os_code += '-HYPERV' bit_check = re.search('\((\d+)\s*bit', description) if bit_check: os_code += '_' + bit_check.group(1) return os_code # Loop through the operating systems and get their OS codes os_list = {} flat_list = [] for os in bmi_options['categories']['os']['items']: if 'Windows Server' in os['description']: os_code = _generate_windows_code(os['description']) else: os_results = os_regex.search(os['description']) name = os_results.group(1) version = os_results.group(2) bits = bit_regex.search(os['description']) extra_info = extra_regex.search(os['description']) if bits: bits = bits.group(1) if extra_info: extra_info = extra_info.group(1) os_code = _generate_os_code(name, version, bits, extra_info) name = os_code.split('_')[0] if name not in os_list: os_list[name] = [] os_list[name].append((os_code, os['price_id'])) flat_list.append((os_code, os['price_id'])) if pretty: results = [] for os in sorted(os_list.keys()): results.append(('os (%s)' % os, os_list[os])) return results else: return [('os', flat_list)] elif 'disk' == section: disks = [] for disk in bmi_options['categories']['disk0']['items']: disks.append((int(disk['capacity']), disk['price_id'])) return [('disks', disks)] elif 'nic' == section: single = [] dual = [] for item in bmi_options['categories']['port_speed']['items']: if 'dual' in item['description'].lower(): dual.append((str(int(item['capacity'])) + '_DUAL', item['price_id'])) else: single.append((int(item['capacity']), item['price_id'])) return [('single nic', single), ('dual nic', dual)] return [] class CreateBMCInstance(CLIRunnable): """ usage: sl bmc create [--disk=DISK...] [--key=KEY...] [options] Order/create a bare metal instance. See 'sl bmc create-options' for valid options NOTE: Due to hardware configurations, the CPU and memory must match appropriately. See create-options for options Required: -c --cpu=CPU Number of CPU cores -D --domain=DOMAIN Domain portion of the FQDN example: example.com -H --hostname=HOST Host portion of the FQDN. example: server -m --memory=MEMORY Memory in mebibytes. Example: 2048 -o OS, --os=OS OS install code --hourly Hourly rate instance type --monthly Monthly rate instance type Optional: -d DC, --datacenter=DC datacenter name Note: Omitting this value defaults to the first available datacenter --dry-run, --test Do not create the instance, just get a quote --export=FILE Exports options to a template file -k KEY, --key=KEY SSH keys to assign to the root user. Can be specified multiple times. -n MBPS, --network=MBPS Network port speed in Mbps --vlan_public=VLAN The ID of the public VLAN on which you want the CCI placed. --vlan_private=VLAN The ID of the private VLAN on which you want the CCI placed. -t, --template=FILE A template file that defaults the command-line options using the long name in INI format """ action = 'create' options = ['confirm'] required_params = ['--hostname', '--domain', '--cpu', '--memory', '--os'] @classmethod def execute(cls, client, args): update_with_template_args(args) mgr = HardwareManager(client) # Disks will be a comma-separated list. Let's make it a real list. if isinstance(args.get('--disk'), str): args['--disk'] = args.get('--disk').split(',') # Do the same thing for SSH keys if isinstance(args.get('--key'), str): args['--key'] = args.get('--key').split(',') cls._validate_args(args) bmi_options = mgr.get_bare_metal_create_options() order = { 'hostname': args['--hostname'], 'domain': args['--domain'], 'bare_metal': True, } # Validate the CPU/Memory combination and get the price ID server_core = cls._get_cpu_and_memory_price_ids(bmi_options, args['--cpu'], args['--memory']) if server_core: order['server'] = server_core else: raise CLIAbort('Invalid CPU/memory combination specified.') order['hourly'] = args['--hourly'] # Convert the OS code back into a price ID os_price = cls._get_price_id_from_options(bmi_options, 'os', args['--os']) if os_price: order['os'] = os_price else: raise CLIAbort('Invalid operating system specified.') order['location'] = args['--datacenter'] or 'FIRST_AVAILABLE' # Set the disk size disk_prices = [] for disk in args.get('--disk'): disk_price = cls._get_price_id_from_options(bmi_options, 'disk', disk) if disk_price: disk_prices.append(disk_price) if not disk_prices: disk_prices.append(cls._get_default_value(bmi_options, 'disk0')) order['disks'] = disk_prices # Set the port speed port_speed = args.get('--network') or 10 nic_price = cls._get_price_id_from_options(bmi_options, 'nic', port_speed) if nic_price: order['port_speed'] = nic_price else: raise CLIAbort('Invalid NIC speed specified.') # Get the SSH keys if args.get('--key'): keys = [] for key in args.get('--key'): key_id = resolve_id(SshKeyManager(client).resolve_ids, key, 'SshKey') keys.append(key_id) order['ssh_keys'] = keys if args.get('--vlan_public'): order['public_vlan'] = args['--vlan_public'] if args.get('--vlan_private'): order['private_vlan'] = args['--vlan_private'] # Begin output t = Table(['Item', 'cost']) t.align['Item'] = 'r' t.align['cost'] = 'r' if args.get('--test'): result = mgr.verify_order(**order) total_monthly = 0.0 total_hourly = 0.0 for price in result['prices']: total_monthly += float(price.get('recurringFee', 0.0)) total_hourly += float(price.get('hourlyRecurringFee', 0.0)) if args.get('--hourly'): rate = "%.2f" % float(price['hourlyRecurringFee']) else: rate = "%.2f" % float(price['recurringFee']) t.add_row([price['item']['description'], rate]) if args.get('--hourly'): total = total_hourly else: total = total_monthly billing_rate = 'monthly' if args.get('--hourly'): billing_rate = 'hourly' t.add_row(['Total %s cost' % billing_rate, "%.2f" % total]) output = SequentialOutput() output.append(t) output.append(FormattedItem( '', ' -- ! Prices reflected here are retail and do not ' 'take account level discounts and are not guaranteed.') ) elif args['--really'] or confirm( "This action will incur charges on your account. Continue?"): result = mgr.place_order(**order) t = KeyValueTable(['name', 'value']) t.align['name'] = 'r' t.align['value'] = 'l' t.add_row(['id', result['orderId']]) t.add_row(['created', result['orderDate']]) output = t else: raise CLIAbort('Aborting bare metal instance order.') return output @classmethod def _validate_args(cls, args): invalid_args = [k for k in cls.required_params if args.get(k) is None] if invalid_args: raise ArgumentError('Missing required options: %s' % ','.join(invalid_args)) if args['--hourly'] in FALSE_VALUES: args['--hourly'] = False if args['--monthly'] in FALSE_VALUES: args['--monthly'] = False if all([args['--hourly'], args['--monthly']]): raise ArgumentError('[--hourly] not allowed with [--monthly]') if not any([args['--hourly'], args['--monthly']]): raise ArgumentError('One of [--hourly | --monthly] is required') @classmethod def _get_cpu_and_memory_price_ids(cls, bmi_options, cpu_value, memory_value): bmi_obj = BMCCreateOptions() price_id = None cpu_regex = re.compile('(\d+)') for k, v in bmi_obj.get_create_options(bmi_options, 'cpu'): cpu = cpu_regex.search(k).group(1) if cpu == cpu_value: for mem_options in v: if mem_options[0] == memory_value: price_id = mem_options[1] return price_id @classmethod def _get_default_value(cls, bmi_options, option): if option not in bmi_options['categories']: return for item in bmi_options['categories'][option]['items']: if not any([ float(item.get('setupFee', 0)), float(item.get('recurringFee', 0)), float(item.get('hourlyRecurringFee', 0)), float(item.get('oneTimeFee', 0)), float(item.get('laborFee', 0)), ]): return item['price_id'] @classmethod def _get_price_id_from_options(cls, bmi_options, option, value): bmi_obj = BMCCreateOptions() price_id = None for k, v in bmi_obj.get_create_options(bmi_options, option, False): for item_options in v: if item_options[0] == value: price_id = item_options[1] return price_id class CancelInstance(CLIRunnable): """ usage: sl bmc cancel [options] Cancel a bare metal instance Options: --immediate Cancels the instance immediately (instead of on the billing anniversary) """ action = 'cancel' options = ['confirm'] @staticmethod def execute(client, args): hw = HardwareManager(client) hw_id = resolve_id( hw.resolve_ids, args.get(''), 'hardware') immediate = args.get('--immediate', False) if args['--really'] or no_going_back(hw_id): hw.cancel_metal(hw_id, immediate) else: CLIAbort('Aborted') softlayer-api-python-client-3.0.1/SoftLayer/CLI/modules/cci.py000077500000000000000000001010451222600344500241660ustar00rootroot00000000000000""" usage: sl cci [] [...] [options] Manage, delete, order compute instances The available commands are: cancel Cancel a running CCI create Order and create a CCI (see `sl cci create-options` for choices) create-options Output available available options when creating a CCI detail Output details about a CCI dns DNS related actions to a CCI edit Edit details of a CCI list List CCI's on the account nic-edit Edit NIC settings pause Pauses an active CCI power-off Powers off a running CCI power-on Boots up a CCI ready Check if a CCI has finished provisioning reboot Reboots a running CCI reload Reload the OS on a CCI based on its current configuration resume Resumes a paused CCI For several commands, will be asked for. This can be the id, hostname or the ip address for a CCI. """ # :copyright: (c) 2013, SoftLayer Technologies, Inc. All rights reserved. # :license: MIT, see LICENSE for more details. from os import linesep import os.path from SoftLayer import CCIManager, SshKeyManager, DNSManager from SoftLayer.utils import lookup from SoftLayer.CLI import ( CLIRunnable, Table, no_going_back, confirm, mb_to_gb, listing, FormattedItem) from SoftLayer.CLI.helpers import ( CLIAbort, ArgumentError, NestedDict, blank, resolve_id, KeyValueTable, update_with_template_args, FALSE_VALUES, export_to_template, active_txn) class ListCCIs(CLIRunnable): """ usage: sl cci list [--hourly | --monthly] [--sortby=SORT_COLUMN] [--tags=TAGS] [options] List CCIs Examples: sl cci list --datacenter=dal05 sl cci list --network=100 --cpu=2 sl cci list --memory='>= 2048' sl cci list --tags=production,db Options: --sortby=ARG Column to sort by. options: id, datacenter, host, Cores, memory, primary_ip, backend_ip Filters: -c --cpu=CPU Number of CPU cores -D --domain=DOMAIN Domain portion of the FQDN. example: example.com -d DC, --datacenter=DC datacenter shortname (sng01, dal05, ...) -H --hostname=HOST Host portion of the FQDN. example: server -m --memory=MEMORY Memory in mebibytes -n MBPS, --network=MBPS Network port speed in Mbps --hourly Show hourly instances --monthly Show monthly instances --tags=ARG Only show instances that have one of these tags. Comma-separated. (production,db) For more on filters see 'sl help filters' """ action = 'list' @staticmethod def execute(client, args): cci = CCIManager(client) tags = None if args.get('--tags'): tags = [tag.strip() for tag in args.get('--tags').split(',')] guests = cci.list_instances( hourly=args.get('--hourly'), monthly=args.get('--monthly'), hostname=args.get('--hostname'), domain=args.get('--domain'), cpus=args.get('--cpu'), memory=args.get('--memory'), datacenter=args.get('--datacenter'), nic_speed=args.get('--network'), tags=tags) t = Table([ 'id', 'datacenter', 'host', 'cores', 'memory', 'primary_ip', 'backend_ip', 'active_transaction', ]) t.sortby = args.get('--sortby') or 'host' for guest in guests: guest = NestedDict(guest) t.add_row([ guest['id'], guest['datacenter']['name'], guest['fullyQualifiedDomainName'], guest['maxCpu'], mb_to_gb(guest['maxMemory']), guest['primaryIpAddress'] or blank(), guest['primaryBackendIpAddress'] or blank(), active_txn(guest), ]) return t class CCIDetails(CLIRunnable): """ usage: sl cci detail [--passwords] [--price] [options] Get details for a CCI Options: --passwords Show passwords (check over your shoulder!) --price Show associated prices """ action = 'detail' @staticmethod def execute(client, args): cci = CCIManager(client) t = KeyValueTable(['Name', 'Value']) t.align['Name'] = 'r' t.align['Value'] = 'l' cci_id = resolve_id(cci.resolve_ids, args.get(''), 'CCI') result = cci.get_instance(cci_id) result = NestedDict(result) t.add_row(['id', result['id']]) t.add_row(['hostname', result['fullyQualifiedDomainName']]) t.add_row(['status', FormattedItem( result['status']['keyName'] or blank(), result['status']['name'] or blank() )]) t.add_row(['active_transaction', active_txn(result)]) t.add_row(['state', FormattedItem( lookup(result, 'powerState', 'keyName'), lookup(result, 'powerState', 'name'), )]) t.add_row(['datacenter', result['datacenter']['name']]) operating_system = lookup(result, 'operatingSystem', 'softwareLicense', 'softwareDescription') t.add_row([ 'os', FormattedItem( operating_system['version'] or blank(), operating_system['name'] or blank() )]) t.add_row(['os_version', operating_system['version'] or blank()]) t.add_row(['cores', result['maxCpu']]) t.add_row(['memory', mb_to_gb(result['maxMemory'])]) t.add_row(['public_ip', result['primaryIpAddress'] or blank()]) t.add_row(['private_ip', result['primaryBackendIpAddress'] or blank()]) t.add_row(['private_only', result['privateNetworkOnlyFlag']]) t.add_row(['private_cpu', result['dedicatedAccountHostOnlyFlag']]) t.add_row(['created', result['createDate']]) t.add_row(['modified', result['modifyDate']]) vlan_table = Table(['type', 'number', 'id']) for vlan in result['networkVlans']: vlan_table.add_row([ vlan['networkSpace'], vlan['vlanNumber'], vlan['id']]) t.add_row(['vlans', vlan_table]) if result.get('notes'): t.add_row(['notes', result['notes']]) if args.get('--price'): t.add_row(['price rate', result['billingItem']['recurringFee']]) if args.get('--passwords'): pass_table = Table(['username', 'password']) for item in result['operatingSystem']['passwords']: pass_table.add_row([item['username'], item['password']]) t.add_row(['users', pass_table]) tag_row = [] for tag in result['tagReferences']: tag_row.append(tag['tag']['name']) if tag_row: t.add_row(['tags', listing(tag_row, separator=',')]) if not result['privateNetworkOnlyFlag']: ptr_domains = client['Virtual_Guest'].\ getReverseDomainRecords(id=cci_id) for ptr_domain in ptr_domains: for ptr in ptr_domain['resourceRecords']: t.add_row(['ptr', ptr['data']]) return t class CreateOptionsCCI(CLIRunnable): """ usage: sl cci create-options [options] Output available available options when creating a CCI Options: --all Show all options. default if no other option provided --cpu Show CPU options --datacenter Show datacenter options --disk Show disk options --memory Show memory size options --nic Show NIC speed options --os Show operating system options """ action = 'create-options' options = ['datacenter', 'cpu', 'nic', 'disk', 'os', 'memory'] @classmethod def execute(cls, client, args): cci = CCIManager(client) result = cci.get_create_options() show_all = True for opt_name in cls.options: if args.get("--" + opt_name): show_all = False break if args['--all']: show_all = True t = KeyValueTable(['Name', 'Value']) t.align['Name'] = 'r' t.align['Value'] = 'l' if args['--datacenter'] or show_all: datacenters = [dc['template']['datacenter']['name'] for dc in result['datacenters']] t.add_row(['datacenter', listing(datacenters, separator=',')]) if args['--cpu'] or show_all: standard_cpu = filter( lambda x: not x['template'].get( 'dedicatedAccountHostOnlyFlag', False), result['processors']) ded_cpu = filter( lambda x: x['template'].get( 'dedicatedAccountHostOnlyFlag', False), result['processors']) def cpus_row(c, name): cpus = [] for x in c: cpus.append(str(x['template']['startCpus'])) t.add_row(['cpus (%s)' % name, listing(cpus, separator=',')]) cpus_row(ded_cpu, 'private') cpus_row(standard_cpu, 'standard') if args['--memory'] or show_all: memory = [ str(m['template']['maxMemory']) for m in result['memory']] t.add_row(['memory', listing(memory, separator=',')]) if args['--os'] or show_all: op_sys = [ o['template']['operatingSystemReferenceCode'] for o in result['operatingSystems']] op_sys = sorted(op_sys) os_summary = set() for o in op_sys: os_summary.add(o[0:o.find('_')]) for summary in sorted(os_summary): t.add_row([ 'os (%s)' % summary, linesep.join(sorted(filter( lambda x: x[0:len(summary)] == summary, op_sys)) ) ]) if args['--disk'] or show_all: local_disks = filter( lambda x: x['template'].get('localDiskFlag', False), result['blockDevices']) san_disks = filter( lambda x: not x['template'].get('localDiskFlag', False), result['blockDevices']) def block_rows(blocks, name): simple = {} for block in blocks: b = block['template']['blockDevices'][0] bid = b['device'] size = b['diskImage']['capacity'] if bid not in simple: simple[bid] = [] simple[bid].append(str(size)) for b in sorted(simple.keys()): t.add_row([ '%s disk(%s)' % (name, b), listing(simple[b], separator=',')] ) block_rows(local_disks, 'local') block_rows(san_disks, 'san') if args['--nic'] or show_all: speeds = [] for x in result['networkComponents']: speed = x['template']['networkComponents'][0]['maxSpeed'] speeds.append(str(speed)) speeds = sorted(speeds) t.add_row(['nic', listing(speeds, separator=',')]) return t class CreateCCI(CLIRunnable): """ usage: sl cci create [--key=KEY...] [options] Order/create a CCI. See 'sl cci create-options' for valid options Required: -c, --cpu=CPU Number of CPU cores -D, --domain=DOMAIN Domain portion of the FQDN. example: example.com -H, --hostname=HOST Host portion of the FQDN. example: server --image=GUID Image GUID. See: 'sl image list' for reference -m, --memory=MEMORY Memory in mebibytes. example: 2048 -o, --os=OS OS install code. Tip: you can specify _LATEST --hourly Hourly rate instance type --monthly Monthly rate instance type Optional: -d, --datacenter=DC Datacenter shortname (sng01, dal05, ...) Note: Omitting this value defaults to the first available datacenter --dedicated Allocate a dedicated CCI (non-shared host) --dry-run, --test Do not create CCI, just get a quote --export=FILE Exports options to a template file -F, --userfile=FILE Read userdata from file (Only HTTPS executes, HTTP leaves file in /root) -i, --postinstall=URI Post-install script to download -k, --key=KEY SSH keys to add to the root user. Can be specified multiple times --like=IDENTIFIER Use the configuration from an existing CCI -n, --network=MBPS Network port speed in Mbps --private Forces the CCI to only have access the private network -t, --template=FILE A template file that defaults the command-line options using the long name in INI format -u, --userdata=DATA User defined metadata string --vlan_public=VLAN The ID of the public VLAN on which you want the CCI placed. --vlan_private=VLAN The ID of the private VLAN on which you want the CCI placed. --wait=SECONDS Block until CCI is finished provisioning for up to X seconds before returning """ action = 'create' options = ['confirm'] required_params = ['--hostname', '--domain', '--cpu', '--memory'] @classmethod def execute(cls, client, args): update_with_template_args(args) cci = CCIManager(client) cls._update_with_like_args(cci, args) # SSH keys may be a comma-separated list. Let's make it a real list. if isinstance(args.get('--key'), str): args['--key'] = args.get('--key').split(',') cls._validate_args(args) # Do not create CCI with --test or --export do_create = not (args['--export'] or args['--test']) t = Table(['Item', 'cost']) t.align['Item'] = 'r' t.align['cost'] = 'r' data = cls._parse_create_args(client, args) output = [] if args.get('--test'): result = cci.verify_create_instance(**data) total_monthly = 0.0 total_hourly = 0.0 t = Table(['Item', 'cost']) t.align['Item'] = 'r' t.align['cost'] = 'r' for price in result['prices']: total_monthly += float(price.get('recurringFee', 0.0)) total_hourly += float(price.get('hourlyRecurringFee', 0.0)) if args.get('--hourly'): rate = "%.2f" % float(price['hourlyRecurringFee']) else: rate = "%.2f" % float(price['recurringFee']) t.add_row([price['item']['description'], rate]) if args.get('--hourly'): total = total_hourly else: total = total_monthly billing_rate = 'monthly' if args.get('--hourly'): billing_rate = 'hourly' t.add_row(['Total %s cost' % billing_rate, "%.2f" % total]) output.append(t) output.append(FormattedItem( None, ' -- ! Prices reflected here are retail and do not ' 'take account level discounts and are not guaranteed.') ) if args['--export']: export_file = args.pop('--export') export_to_template(export_file, args, exclude=['--wait', '--test']) return 'Successfully exported options to a template file.' if do_create: if args['--really'] or confirm( "This action will incur charges on your account. " "Continue?"): result = cci.create_instance(**data) t = KeyValueTable(['name', 'value']) t.align['name'] = 'r' t.align['value'] = 'l' t.add_row(['id', result['id']]) t.add_row(['created', result['createDate']]) t.add_row(['guid', result['globalIdentifier']]) output.append(t) if args.get('--wait'): ready = cci.wait_for_transaction( result['id'], int(args.get('--wait') or 1)) t.add_row(['ready', ready]) else: raise CLIAbort('Aborting CCI order.') return output @classmethod def _validate_args(cls, args): invalid_args = [k for k in cls.required_params if args.get(k) is None] if invalid_args: raise ArgumentError('Missing required options: %s' % ','.join(invalid_args)) if all([args['--userdata'], args['--userfile']]): raise ArgumentError('[-u | --userdata] not allowed with ' '[-F | --userfile]') if args['--hourly'] in FALSE_VALUES: args['--hourly'] = False if args['--monthly'] in FALSE_VALUES: args['--monthly'] = False if all([args['--hourly'], args['--monthly']]): raise ArgumentError('[--hourly] not allowed with [--monthly]') if not any([args['--hourly'], args['--monthly']]): raise ArgumentError('One of [--hourly | --monthly] is required') image_args = [args['--os'], args['--image']] if all(image_args): raise ArgumentError('[-o | --os] not allowed with [--image]') if not any(image_args): raise ArgumentError('One of [--os | --image] is required') if args['--userfile']: if not os.path.exists(args['--userfile']): raise ArgumentError( 'File does not exist [-u | --userfile] = %s' % args['--userfile']) @staticmethod def _update_with_like_args(cci, args): """ Update arguments with options taken from a currently running CCI. :param CCIManager args: A CCIManager :param dict args: CLI arguments """ if args['--like']: cci_id = resolve_id(cci.resolve_ids, args.pop('--like'), 'CCI') like_details = cci.get_instance(cci_id) like_args = { '--hostname': like_details['hostname'], '--domain': like_details['domain'], '--cpu': like_details['maxCpu'], '--memory': like_details['maxMemory'], '--hourly': like_details['hourlyBillingFlag'], '--monthly': not like_details['hourlyBillingFlag'], '--datacenter': like_details['datacenter']['name'], '--network': like_details['networkComponents'][0]['maxSpeed'], '--user-data': like_details['userData'] or None, '--postinstall': like_details.get('postInstallScriptUri'), '--dedicated': like_details['dedicatedAccountHostOnlyFlag'], '--private': like_details['privateNetworkOnlyFlag'], } # Handle mutually exclusive options like_image = lookup(like_details, 'blockDeviceTemplateGroup', 'globalIdentifier') like_os = lookup(like_details, 'operatingSystem', 'softwareLicense', 'softwareDescription', 'referenceCode') if like_image and not args.get('--os'): like_args['--image'] = like_image elif like_os and not args.get('--image'): like_args['--os'] = like_os if args.get('--hourly'): like_args['--monthly'] = False if args.get('--monthly'): like_args['--hourly'] = False # Merge like CCI options with the options passed in for key, value in like_args.items(): if args.get(key) in [None, False]: args[key] = value @staticmethod def _parse_create_args(client, args): """ Converts CLI arguments to arguments that can be passed into CCIManager.create_instance. :param dict args: CLI arguments """ data = { "hourly": args['--hourly'], "cpus": args['--cpu'], "domain": args['--domain'], "hostname": args['--hostname'], "private": args['--private'], "dedicated": args['--dedicated'], "local_disk": True, } try: memory = int(args['--memory']) if memory < 1024: memory = memory * 1024 except ValueError: unit = args['--memory'][-1] memory = int(args['--memory'][0:-1]) if unit in ['G', 'g']: memory = memory * 1024 if unit in ['T', 'r']: memory = memory * 1024 * 1024 data["memory"] = memory if args['--monthly']: data['hourly'] = False if args.get('--os'): data['os_code'] = args['--os'] if args.get('--image'): data['image_id'] = args['--image'] if args.get('--datacenter'): data['datacenter'] = args['--datacenter'] if args.get('--network'): data['nic_speed'] = args.get('--network') if args.get('--userdata'): data['userdata'] = args['--userdata'] elif args.get('--userfile'): f = open(args['--userfile'], 'r') try: data['userdata'] = f.read() finally: f.close() if args.get('--postinstall'): data['post_uri'] = args.get('--postinstall') # Get the SSH keys if args.get('--key'): keys = [] for key in args.get('--key'): key_id = resolve_id(SshKeyManager(client).resolve_ids, key, 'SshKey') keys.append(key_id) data['ssh_keys'] = keys if args.get('--vlan_public'): data['public_vlan'] = args['--vlan_public'] if args.get('--vlan_private'): data['private_vlan'] = args['--vlan_private'] return data class ReadyCCI(CLIRunnable): """ usage: sl cci ready [options] Check if a CCI is ready. Optional: --wait=SECONDS Block until CCI is finished provisioning for up to X seconds before returning. """ action = 'ready' @staticmethod def execute(client, args): cci = CCIManager(client) cci_id = resolve_id(cci.resolve_ids, args.get(''), 'CCI') ready = cci.wait_for_transaction(cci_id, int(args.get('--wait') or 0)) if ready: return "READY" else: raise CLIAbort("Instance %s not ready" % cci_id) class ReloadCCI(CLIRunnable): """ usage: sl cci reload [--key=KEY...] [options] Reload the OS on a CCI based on its current configuration Optional: -i, --postinstall=URI Post-install script to download (Only HTTPS executes, HTTP leaves file in /root) -k, --key=KEY SSH keys to add to the root user. Can be specified multiple times """ action = 'reload' options = ['confirm'] @staticmethod def execute(client, args): cci = CCIManager(client) cci_id = resolve_id(cci.resolve_ids, args.get(''), 'CCI') keys = [] if args.get('--key'): for key in args.get('--key'): key_id = resolve_id(SshKeyManager(client).resolve_ids, key, 'SshKey') keys.append(key_id) if args['--really'] or no_going_back(cci_id): cci.reload_instance(cci_id, args['--postinstall'], keys) else: CLIAbort('Aborted') class CancelCCI(CLIRunnable): """ usage: sl cci cancel [options] Cancel a CCI """ action = 'cancel' options = ['confirm'] @staticmethod def execute(client, args): cci = CCIManager(client) cci_id = resolve_id(cci.resolve_ids, args.get(''), 'CCI') if args['--really'] or no_going_back(cci_id): cci.cancel_instance(cci_id) else: CLIAbort('Aborted') class CCIPowerOff(CLIRunnable): """ usage: sl cci power-off [--hard] [options] Power off an active CCI Optional: --hard Perform a hard shutdown """ action = 'power-off' options = ['confirm'] @classmethod def execute(cls, client, args): vg = client['Virtual_Guest'] cci = CCIManager(client) cci_id = resolve_id(cci.resolve_ids, args.get(''), 'CCI') if args['--really'] or confirm('This will power off the CCI with id ' '%s. Continue?' % cci_id): if args['--hard']: vg.powerOff(id=cci_id) else: vg.powerOffSoft(id=cci_id) else: raise CLIAbort('Aborted.') class CCIReboot(CLIRunnable): """ usage: sl cci reboot [--hard | --soft] [options] Reboot an active CCI Optional: --hard Perform an abrupt reboot --soft Perform a graceful reboot """ action = 'reboot' options = ['confirm'] @classmethod def execute(cls, client, args): vg = client['Virtual_Guest'] cci = CCIManager(client) cci_id = resolve_id(cci.resolve_ids, args.get(''), 'CCI') if args['--really'] or confirm('This will reboot the CCI with id ' '%s. Continue?' % cci_id): if args['--hard']: vg.rebootHard(id=cci_id) elif args['--soft']: vg.rebootSoft(id=cci_id) else: vg.rebootDefault(id=cci_id) else: raise CLIAbort('Aborted.') class CCIPowerOn(CLIRunnable): """ usage: sl cci power-on [options] Power on a CCI """ action = 'power-on' @classmethod def execute(cls, client, args): vg = client['Virtual_Guest'] cci = CCIManager(client) cci_id = resolve_id(cci.resolve_ids, args.get(''), 'CCI') vg.powerOn(id=cci_id) class CCIPause(CLIRunnable): """ usage: sl cci pause [options] Pauses an active CCI """ action = 'pause' options = ['confirm'] @classmethod def execute(cls, client, args): vg = client['Virtual_Guest'] cci = CCIManager(client) cci_id = resolve_id(cci.resolve_ids, args.get(''), 'CCI') if args['--really'] or confirm('This will pause the CCI with id ' '%s. Continue?' % cci_id): vg.pause(id=cci_id) else: raise CLIAbort('Aborted.') class CCIResume(CLIRunnable): """ usage: sl cci resume [options] Resumes a paused CCI """ action = 'resume' @classmethod def execute(cls, client, args): vg = client['Virtual_Guest'] cci = CCIManager(client) cci_id = resolve_id(cci.resolve_ids, args.get(''), 'CCI') vg.resume(id=cci_id) class NicEditCCI(CLIRunnable): """ usage: sl cci nic-edit (public | private) --speed=SPEED [options] Manage NIC settings Options: --speed=SPEED Port speed. 0 disables the port. [Options: 0, 10, 100, 1000, 10000] """ action = 'nic-edit' @classmethod def execute(cls, client, args): public = args['public'] cci = CCIManager(client) cci_id = resolve_id(cci.resolve_ids, args.get(''), 'CCI') cci.change_port_speed(cci_id, public, args['--speed']) class CCIDNS(CLIRunnable): """ usage: sl cci dns sync [options] Attempts to update DNS for the specified CCI. If you don't specify any arguments, it will attempt to update both the A and PTR records. If you don't want to update both records, you may use the -a or --ptr arguments to limit the records updated. Options: -a Sync the A record for the host --ptr Sync the PTR record for the host --ttl=TTL Sets the TTL for the A and/or PTR records """ action = 'dns' options = ['confirm'] @classmethod def execute(cls, client, args): args['--ttl'] = args['--ttl'] or DNSManager.DEFAULT_TTL if args['sync']: return cls.dns_sync(client, args) @staticmethod def dns_sync(client, args): from SoftLayer import DNSManager, DNSZoneNotFound dns = DNSManager(client) cci = CCIManager(client) cci_id = resolve_id(cci.resolve_ids, args.get(''), 'CCI') instance = cci.get_instance(cci_id) zone_id = resolve_id(dns.resolve_ids, instance['domain'], name='zone') def sync_a_record(): records = dns.get_records( zone_id, host=instance['hostname'], ) if not records: # don't have a record, lets add one to the base zone dns.create_record( zone['id'], instance['hostname'], 'a', instance['primaryIpAddress'], ttl=args['--ttl']) else: recs = filter(lambda x: x['type'].lower() == 'a', records) if len(recs) != 1: raise CLIAbort("Aborting A record sync, found %d " "A record exists!" % len(recs)) rec = recs[0] rec['data'] = instance['primaryIpAddress'] rec['ttl'] = args['--ttl'] dns.edit_record(rec) def sync_ptr_record(): host_rec = instance['primaryIpAddress'].split('.')[-1] ptr_domains = client['Virtual_Guest'].\ getReverseDomainRecords(id=instance['id'])[0] edit_ptr = None for ptr in ptr_domains['resourceRecords']: if ptr['host'] == host_rec: ptr['ttl'] = args['--ttl'] edit_ptr = ptr break if edit_ptr: edit_ptr['data'] = instance['fullyQualifiedDomainName'] dns.edit_record(edit_ptr) else: dns.create_record( ptr_domains['id'], host_rec, 'ptr', instance['fullyQualifiedDomainName'], ttl=args['--ttl']) if not instance['primaryIpAddress']: raise CLIAbort('No primary IP address associated with this CCI') try: zone = dns.get_zone(zone_id) except DNSZoneNotFound: raise CLIAbort("Unable to create A record, " "no zone found matching: %s" % instance['domain']) go_for_it = args['--really'] or confirm( "Attempt to update DNS records for %s" % instance['fullyQualifiedDomainName']) if not go_for_it: raise CLIAbort("Aborting DNS sync") both = False if not args['--ptr'] and not args['-a']: both = True if both or args['-a']: sync_a_record() if both or args['--ptr']: sync_ptr_record() class EditCCI(CLIRunnable): """ usage: sl cci edit [options] Edit CCI details Options: -D --domain=DOMAIN Domain portion of the FQDN example: example.com -F --userfile=FILE Read userdata from file -H --hostname=HOST Host portion of the FQDN. example: server -u --userdata=DATA User defined metadata string """ action = 'edit' @staticmethod def execute(client, args): data = {} if args['--userdata'] and args['--userfile']: raise ArgumentError('[-u | --userdata] not allowed with ' '[-F | --userfile]') if args['--userfile']: if not os.path.exists(args['--userfile']): raise ArgumentError( 'File does not exist [-u | --userfile] = %s' % args['--userfile']) if args.get('--userdata'): data['userdata'] = args['--userdata'] elif args.get('--userfile'): f = open(args['--userfile'], 'r') try: data['userdata'] = f.read() finally: f.close() data['hostname'] = args.get('--hostname') data['domain'] = args.get('--domain') cci = CCIManager(client) cci_id = resolve_id(cci.resolve_ids, args.get(''), 'CCI') if not cci.edit(cci_id, **data): raise CLIAbort("Failed to update CCI") softlayer-api-python-client-3.0.1/SoftLayer/CLI/modules/config.py000066400000000000000000000125321222600344500246740ustar00rootroot00000000000000""" usage: sl config [] [...] [options] View and edit configuration The available commands are: setup Setup configuration show Show current configuration """ # :copyright: (c) 2013, SoftLayer Technologies, Inc. All rights reserved. # :license: MIT, see LICENSE for more details. import os.path from SoftLayer import ( Client, SoftLayerAPIError, API_PUBLIC_ENDPOINT, API_PRIVATE_ENDPOINT) from SoftLayer.CLI import ( CLIRunnable, CLIAbort, KeyValueTable, confirm, format_output) import ConfigParser def get_settings_from_client(client): """ Pull out settings from a SoftLayer.Client instance. :param client: SoftLayer.Client instance """ settings = { 'username': '', 'api_key': '', 'timeout': client.timeout or '', 'endpoint_url': client.endpoint_url, } try: settings['username'] = client.auth.username settings['api_key'] = client.auth.api_key except AttributeError: pass return settings def config_table(settings): t = KeyValueTable(['Name', 'Value']) t.align['Name'] = 'r' t.align['Value'] = 'l' t.add_row(['Username', settings['username'] or 'not set']) t.add_row(['API Key', settings['api_key'] or 'not set']) t.add_row(['Endpoint URL', settings['endpoint_url'] or 'not set']) t.add_row(['Timeout', settings['timeout'] or 'not set']) return t def get_api_key(username, secret, endpoint_url=None): # Try to use a client with username/api key try: client = Client( username=username, api_key=secret, endpoint_url=endpoint_url, timeout=5) client['Account'].getCurrentUser() return secret except SoftLayerAPIError as e: if 'invalid api token' not in e.faultString.lower(): raise # Try to use a client with username/password client = Client(endpoint_url=endpoint_url, timeout=5) client.authenticate_with_password(username, secret) user_record = client['Account'].getCurrentUser( mask='id, apiAuthenticationKeys') api_keys = user_record['apiAuthenticationKeys'] if len(api_keys) == 0: return client['User_Customer'].addApiAuthenticationKey( id=user_record['id']) return api_keys[0]['authenticationKey'] class Setup(CLIRunnable): """ usage: sl config setup [options] Setup configuration """ action = 'setup' @classmethod def execute(cls, client, args): settings = get_settings_from_client(client) # User Input # Ask for username while True: username = cls.env.input( 'Username [%s]: ' % settings['username']) \ or settings['username'] if username: break # Ask for 'secret' which can be api_key or their password while True: secret = cls.env.getpass( 'API Key or Password [%s]: ' % settings['api_key']) \ or settings['api_key'] if secret: break # Ask for which endpoint they want to use while True: endpoint_type = cls.env.input('Endpoint (public|private|custom): ') endpoint_type = endpoint_type.lower() if not endpoint_type: endpoint_url = API_PUBLIC_ENDPOINT break if endpoint_type == 'public': endpoint_url = API_PUBLIC_ENDPOINT break elif endpoint_type == 'private': endpoint_url = API_PRIVATE_ENDPOINT break elif endpoint_type == 'custom': endpoint_url = cls.env.input( 'Endpoint URL [%s]: ' % settings['endpoint_url'] ) or settings['endpoint_url'] break api_key = get_api_key(username, secret, endpoint_url=endpoint_url) settings['username'] = username settings['api_key'] = api_key settings['endpoint_url'] = endpoint_url path = '~/.softlayer' if args.get('--config'): path = args.get('--config') config_path = os.path.expanduser(path) cls.env.out(format_output(config_table(settings))) if not confirm('Are you sure you want to write settings to "%s"?' % config_path, default=True): raise CLIAbort('Aborted.') # Persist the config file. Read the target config file in before # setting the values to avoid clobbering settings config = ConfigParser.RawConfigParser() config.read(config_path) try: config.add_section('softlayer') except ConfigParser.DuplicateSectionError: pass config.set('softlayer', 'username', settings['username']) config.set('softlayer', 'api_key', settings['api_key']) config.set('softlayer', 'endpoint_url', settings['endpoint_url']) f = os.fdopen(os.open( config_path, (os.O_WRONLY | os.O_CREAT), 0600), 'w') try: config.write(f) finally: f.close() return "Configuration Updated Successfully" class Show(CLIRunnable): """ usage: sl config show [options] Show current configuration """ action = 'show' @classmethod def execute(cls, client, args): settings = get_settings_from_client(client) return config_table(settings) softlayer-api-python-client-3.0.1/SoftLayer/CLI/modules/dns.py000077500000000000000000000154441222600344500242230ustar00rootroot00000000000000""" usage: sl dns [] [...] [options] Manage DNS The available zone commands are: create Create zone delete Delete zone list List zones or a zone's records print Print zone in BIND format The available record commands are: add Add resource record edit Update resource records (bulk/single) remove Remove resource records """ # :copyright: (c) 2013, SoftLayer Technologies, Inc. All rights reserved. # :license: MIT, see LICENSE for more details. from SoftLayer.CLI import ( CLIRunnable, no_going_back, Table, CLIAbort, resolve_id) from SoftLayer import DNSManager, DNSZoneNotFound class DumpZone(CLIRunnable): """ usage: sl dns print [options] print zone in BIND format Arguments: Zone name (softlayer.com) """ action = "print" @staticmethod def execute(client, args): manager = DNSManager(client) zone_id = resolve_id(manager.resolve_ids, args[''], name='zone') try: return manager.dump_zone(zone_id) except DNSZoneNotFound: raise CLIAbort("No zone found matching: %s" % args['']) class CreateZone(CLIRunnable): """ usage: sl dns create [options] Create a zone Arguments: Zone name (softlayer.com) """ action = 'create' @staticmethod def execute(client, args): manager = DNSManager(client) manager.create_zone(args['']) class DeleteZone(CLIRunnable): """ usage: sl dns delete [options] Delete zone Arguments: Zone name (softlayer.com) """ action = 'delete' options = ['confirm'] @staticmethod def execute(client, args): manager = DNSManager(client) zone_id = resolve_id(manager.resolve_ids, args[''], name='zone') if args['--really'] or no_going_back(args['']): manager.delete_zone(zone_id) else: raise CLIAbort("Aborted.") class ListZones(CLIRunnable): """ usage: sl dns list [] [options] List zones and optionally, records Filters: --data=DATA Record data, such as an IP address --record=HOST Host record, such as www --ttl=TTL TTL value in seconds, such as 86400 --type=TYPE Record type, such as A or CNAME """ action = 'list' @classmethod def execute(cls, client, args): if args['']: return cls.list_zone(client, args[''], args) return cls.list_all_zones(client) @staticmethod def list_zone(client, zone, args): manager = DNSManager(client) t = Table([ "record", "type", "ttl", "value", ]) t.align['ttl'] = 'l' t.align['record'] = 'r' t.align['value'] = 'l' zone_id = resolve_id(manager.resolve_ids, args[''], name='zone') try: records = manager.get_records( zone_id, type=args.get('--type'), host=args.get('--record'), ttl=args.get('--ttl'), data=args.get('--data'), ) except DNSZoneNotFound: raise CLIAbort("No zone found matching: %s" % args['']) for rr in records: t.add_row([ rr['host'], rr['type'].upper(), rr['ttl'], rr['data'] ]) return t @staticmethod def list_all_zones(client): manager = DNSManager(client) zones = manager.list_zones() t = Table([ "id", "zone", "serial", "updated", ]) t.align['serial'] = 'c' t.align['updated'] = 'c' for z in zones: t.add_row([ z['id'], z['name'], z['serial'], z['updateDate'], ]) return t class AddRecord(CLIRunnable): """ usage: sl dns add [--ttl=TTL] [options] Add resource record Arguments: Zone name (softlayer.com) Resource record (www) Record type. [Options: A, AAAA, CNAME, MX, NS, PTR, SPF, SRV, TXT] Record data. NOTE: only minor validation is done Options: --ttl=TTL Time to live """ action = 'add' @staticmethod def execute(client, args): manager = DNSManager(client) zone_id = resolve_id(manager.resolve_ids, args[''], name='zone') args['--ttl'] = args['--ttl'] or DNSManager.DEFAULT_TTL manager.create_record( zone_id, args[''], args[''], args[''], ttl=args['--ttl']) class EditRecord(CLIRunnable): """ usage: sl dns edit [--data=DATA] [--ttl=TTL] [--id=ID] [options] Update resource records (bulk/single) Arguments: Zone name (softlayer.com) Resource record (www) Options: --data=DATA --id=ID Modify only the given ID --ttl=TTL Time to live """ action = 'edit' @staticmethod def execute(client, args): manager = DNSManager(client) zone_id = resolve_id(manager.resolve_ids, args[''], name='zone') try: results = manager.get_records( zone_id, host=args['']) except DNSZoneNotFound: raise CLIAbort("No zone found matching: %s" % args['']) for r in results: if args['--id'] and r['id'] != args['--id']: continue r['data'] = args['--data'] or r['data'] r['ttl'] = args['--ttl'] or r['ttl'] manager.edit_record(r) class RecordRemove(CLIRunnable): """ usage: sl dns remove [--id=ID] [options] Remove resource records Arguments: Zone name (softlayer.com) Resource record (www) Options: --id=ID Remove only the given ID """ action = 'remove' options = ['confirm'] @staticmethod def execute(client, args): manager = DNSManager(client) zone_id = resolve_id(manager.resolve_ids, args[''], name='zone') if args['--id']: records = [{'id': args['--id']}] else: try: records = manager.get_records( zone_id, host=args['']) except DNSZoneNotFound: raise CLIAbort("No zone found matching: %s" % args['']) if args['--really'] or no_going_back('yes'): t = Table(['record']) for r in records: if args.get('--id') and args['--id'] != r['id']: continue manager.delete_record(r['id']) t.add_row([r['id']]) return t raise CLIAbort("Aborted.") softlayer-api-python-client-3.0.1/SoftLayer/CLI/modules/filters.py000066400000000000000000000017521222600344500251010ustar00rootroot00000000000000""" usage: sl help filters Filters are used to limit the amount of results. Some commands will accept a filter operation for certain fields. Filters can be applied across multiple fields in most cases. Available Operations: Case Insensitive 'value' Exact value match 'value*' Begins with value '*value' Ends with value '*value*' Contains value Case Sensitive '~ value' Exact value match '> value' Greater than value '< value' Less than value '>= value' Greater than or equal to value '<= value' Less than or equal to value Examples: sl server list --datacenter=dal05 sl server list --hostname='prod*' sl cci list --network=100 --cpu=2 sl cci list --network='< 100' --cpu=2 sl cci list --memory='>= 2048' Note: Comparison operators (>, <, >=, <=) can be used with integers, floats, and strings. """ # :copyright: (c) 2013, SoftLayer Technologies, Inc. All rights reserved. # :license: MIT, see LICENSE for more details. softlayer-api-python-client-3.0.1/SoftLayer/CLI/modules/firewall.py000077500000000000000000000026271222600344500252430ustar00rootroot00000000000000""" usage: sl firewall [] [...] [options] Firewall rule and security management The available commands are: list List active vlans with firewalls """ # :copyright: (c) 2013, SoftLayer Technologies, Inc. All rights reserved. # :license: MIT, see LICENSE for more details. from SoftLayer.CLI import CLIRunnable, Table, listing from SoftLayer.CLI.helpers import blank from SoftLayer import FirewallManager class FWList(CLIRunnable): """ usage: sl firewall list [options] List active vlans with firewalls """ action = 'list' @staticmethod def execute(client, args): f = FirewallManager(client) fwvlans = f.get_firewalls() t = Table(['vlan', 'type', 'features']) dedicatedfws = filter(lambda x: x['dedicatedFirewallFlag'], fwvlans) for vlan in dedicatedfws: features = [] if vlan['highAvailabilityFirewallFlag']: features.append('HA') if features: feature_list = listing(features, separator=',') else: feature_list = blank() t.add_row([ vlan['vlanNumber'], 'dedicated', feature_list, ]) shared_vlan = filter(lambda x: not x['dedicatedFirewallFlag'], fwvlans) for vlan in shared_vlan: t.add_row([vlan['vlanNumber'], 'standard', blank()]) return t softlayer-api-python-client-3.0.1/SoftLayer/CLI/modules/globalip.py000066400000000000000000000121561222600344500252220ustar00rootroot00000000000000""" usage: sl globalip [] [...] [options] Orders or configures global IP addresses The available commands are: assign Assign a target to a global IP address cancel Cancels a global IP create Orders a new global IP address list Display a list of global IP addresses unassign Unassigns a global IP """ # :copyright: (c) 2013, SoftLayer Technologies, Inc. All rights reserved. # :license: MIT, see LICENSE for more details. from SoftLayer import NetworkManager from SoftLayer.CLI import ( CLIRunnable, Table, FormattedItem, confirm, no_going_back) from SoftLayer.CLI.helpers import CLIAbort, SequentialOutput class GlobalIpAssign(CLIRunnable): """ usage: sl globalip assign [options] Assigns a global IP to a target. Required: The ID or address of the global IP The IP address to assign to the global IP """ action = 'assign' @staticmethod def execute(client, args): mgr = NetworkManager(client) id = mgr.resolve_global_ip_ids(args.get('')) if not id: raise CLIAbort("Unable to find global IP record for " + args['']) mgr.assign_global_ip(id, args['']) class GlobalIpCancel(CLIRunnable): """ usage: sl globalip cancel [options] Cancel a subnet """ action = 'cancel' options = ['confirm'] @staticmethod def execute(client, args): mgr = NetworkManager(client) id = mgr.resolve_global_ip_ids(args.get('')) if args['--really'] or no_going_back(id): mgr.cancel_global_ip(id) else: CLIAbort('Aborted') class GlobalIpCreate(CLIRunnable): """ usage: sl globalip create [options] Add a new global IP address to your account. Options: --v6 Orders IPv6 --dry-run, --test Do not order the IP; just get a quote """ action = 'create' options = ['confirm'] @staticmethod def execute(client, args): mgr = NetworkManager(client) version = 4 if args.get('--v6'): version = 6 if not args.get('--test') and not args['--really']: if not confirm("This action will incur charges on your account." "Continue?"): raise CLIAbort('Cancelling order.') result = mgr.add_global_ip(version=version, test_order=args.get('--test')) if not result: return 'Unable to place order: No valid price IDs found.' t = Table(['Item', 'cost']) t.align['Item'] = 'r' t.align['cost'] = 'r' total = 0.0 for price in result['orderDetails']['prices']: total += float(price.get('recurringFee', 0.0)) rate = "%.2f" % float(price['recurringFee']) t.add_row([price['item']['description'], rate]) t.add_row(['Total monthly cost', "%.2f" % total]) output = SequentialOutput() output.append(t) output.append(FormattedItem( '', ' -- ! Prices reflected here are retail and do not ' 'take account level discounts and are not guarenteed.') ) return t class GlobalIpList(CLIRunnable): """ usage: sl globalip list [options] Displays a list of global IPs Filters: --v4 Display only IPV4 --v6 Display only IPV6 """ action = 'list' @staticmethod def execute(client, args): mgr = NetworkManager(client) t = Table([ 'id', 'ip', 'assigned', 'target' ]) t.sortby = args.get('--sortby') or 'id' version = 0 if args.get('--v4'): version = 4 elif args.get('--v6'): version = 6 ips = mgr.list_global_ips(version=version) for ip in ips: assigned = 'No' target = 'None' if ip.get('destinationIpAddress'): dest = ip['destinationIpAddress'] assigned = 'Yes' target = dest['ipAddress'] if dest.get('virtualGuest'): vg = dest['virtualGuest'] target += ' (' + vg['fullyQualifiedDomainName'] + ')' elif ip['destinationIpAddress'].get('hardware'): target += ' (' + \ dest['hardware']['fullyQualifiedDomainName'] + \ ')' t.add_row([ip['id'], ip['ipAddress']['ipAddress'], assigned, target]) return t class GlobalIpUnassign(CLIRunnable): """ usage: sl globalip unassign [options] Unassigns a global IP from a target. Required: The ID or address of the global IP """ action = 'unassign' @staticmethod def execute(client, args): mgr = NetworkManager(client) id = mgr.resolve_global_ip_ids(args.get('')) if not id: raise CLIAbort("Unable to find global IP record for " + args['']) mgr.unassign_global_ip(id) softlayer-api-python-client-3.0.1/SoftLayer/CLI/modules/help.py000066400000000000000000000015141222600344500243550ustar00rootroot00000000000000""" usage: sl help [options] sl help [options] sl help [options] View help on a module or command. """ # :copyright: (c) 2013, SoftLayer Technologies, Inc. All rights reserved. # :license: MIT, see LICENSE for more details. from SoftLayer.CLI.core import CommandParser from SoftLayer.CLI import CLIRunnable class Show(CLIRunnable): # Use the same documentation as the module __doc__ = __doc__ action = None @classmethod def execute(cls, client, args): parser = CommandParser(cls.env) cls.env.load_module(args['']) if args['']: return parser.get_command_help(args[''], args['']) elif args['']: return parser.get_module_help(args['']) return parser.get_main_help() softlayer-api-python-client-3.0.1/SoftLayer/CLI/modules/image.py000066400000000000000000000032561222600344500245140ustar00rootroot00000000000000""" usage: sl image [] [...] [options] Manage compute and flex images The available commands are: list List images """ # :copyright: (c) 2013, SoftLayer Technologies, Inc. All rights reserved. # :license: MIT, see LICENSE for more details. from SoftLayer.CLI import CLIRunnable, Table from SoftLayer.CLI.helpers import blank class ListImages(CLIRunnable): """ usage: sl image list [--public | --private] [options] List images Options: --private Display only private images --public Display only public images """ action = 'list' @staticmethod def execute(client, args): account = client['Account'] neither = not any([args['--private'], args['--public']]) results = [] if args['--private'] or neither: account = client['Account'] mask = 'id,accountId,name,globalIdentifier,blockDevices,parentId' r = account.getPrivateBlockDeviceTemplateGroups(mask=mask) results.append(r) if args['--public'] or neither: vgbd = client['Virtual_Guest_Block_Device_Template_Group'] r = vgbd.getPublicImages() results.append(r) t = Table(['id', 'account', 'type', 'name', 'guid', ]) t.sortby = 'name' for result in results: images = filter(lambda x: x['parentId'] == '', result) for image in images: t.add_row([ image['id'], image.get('accountId', blank()), image.get('type', blank()), image['name'].strip(), image.get('globalIdentifier', blank()), ]) return t softlayer-api-python-client-3.0.1/SoftLayer/CLI/modules/iscsi.py000066400000000000000000000025111222600344500245350ustar00rootroot00000000000000""" usage: sl iscsi [] [...] [options] Manage iSCSI targets The available commands are: list List iSCSI targets """ # :copyright: (c) 2013, SoftLayer Technologies, Inc. All rights reserved. # :license: MIT, see LICENSE for more details. from SoftLayer.CLI import CLIRunnable, Table, FormattedItem from SoftLayer.CLI.helpers import NestedDict, blank class ListISCSI(CLIRunnable): """ usage: sl iscsi list [options] List iSCSI accounts """ action = 'list' @staticmethod def execute(client, args): account = client['Account'] iscsi = account.getIscsiNetworkStorage( mask='eventCount,serviceResource[datacenter.name]') iscsi = [NestedDict(n) for n in iscsi] t = Table([ 'id', 'datacenter', 'size', 'username', 'password', 'server' ]) for n in iscsi: t.add_row([ n['id'], n['serviceResource']['datacenter'].get('name', blank()), FormattedItem( n.get('capacityGb', blank()), "%dGB" % n.get('capacityGb', 0)), n.get('username', blank()), n.get('password', blank()), n.get('serviceResourceBackendIpAddress', blank())]) return t softlayer-api-python-client-3.0.1/SoftLayer/CLI/modules/messaging.py000066400000000000000000000361451222600344500254120ustar00rootroot00000000000000""" usage: sl messaging [] [...] [options] Manage the SoftLayer Message Queue service. For most commands, a queue account is required. Use 'sl messaging accounts-list' to list current accounts The available commands are: accounts-list List all queue accounts endpoints-list List all service endpoints ping Ping the service queue-add Create a new queue queue-detail Prints the details of a queue queue-edit Modifies an existing queue queue-list Lists out all queues on an account queue-pop Pop a message from a queue queue-push Pushes a message into a queue queue-remove Delete a queue topic-add Creates a new topic topic-detail Prints the details of a topic topic-list Lists out all topics on an account topic-push Pushes a notification to a topic topic-remove Deletes a topic topic-subscribe Adds a subscription on a topic topic-unsubscribe Remove a subscription on a topic """ # :copyright: (c) 2013, SoftLayer Technologies, Inc. All rights reserved. # :license: MIT, see LICENSE for more details. import sys from SoftLayer import MessagingManager from SoftLayer.CLI import CLIRunnable, Table from SoftLayer.CLI.helpers import CLIAbort, listing, ArgumentError, blank COMMON_MESSAGING_ARGS = """Service Options: --datacenter=NAME Datacenter, E.G.: dal05 --network=TYPE Network type, [Options: public, private] """ class ListAccounts(CLIRunnable): """ usage: sl messaging accounts-list [options] List SoftLayer Message Queue Accounts """ action = 'accounts-list' @staticmethod def execute(client, args): manager = MessagingManager(client) accounts = manager.list_accounts() t = Table([ 'id', 'name', 'status' ]) for account in accounts: t.add_row([ account['nodes'][0]['accountName'], account['name'], account['status']['name'], ]) return t class ListEndpoints(CLIRunnable): """ usage: sl messaging endpoints-list [options] List SoftLayer Message Queue Endpoints """ action = 'endpoints-list' @staticmethod def execute(client, args): manager = MessagingManager(client) regions = manager.get_endpoints() t = Table([ 'name', 'public', 'private' ]) for region, endpoints in regions.items(): t.add_row([ region, endpoints.get('public') or blank(), endpoints.get('private') or blank(), ]) return t class Ping(CLIRunnable): __doc__ = """ usage: sl messaging ping [options] Ping the SoftLayer Message Queue service """ + COMMON_MESSAGING_ARGS action = 'ping' @staticmethod def execute(client, args): manager = MessagingManager(client) ok = manager.ping( datacenter=args['--datacenter'], network=args['--network']) if ok: return 'OK' else: CLIAbort('Ping failed') def queue_table(queue): t = Table(['property', 'value']) t.align['property'] = 'r' t.align['value'] = 'l' t.add_row(['name', queue['name']]) t.add_row(['message_count', queue['message_count']]) t.add_row(['visible_message_count', queue['visible_message_count']]) t.add_row(['tags', listing(queue['tags'] or [])]) t.add_row(['expiration', queue['expiration']]) t.add_row(['visibility_interval', queue['visibility_interval']]) return t def message_table(message): t = Table(['property', 'value']) t.align['property'] = 'r' t.align['value'] = 'l' t.add_row(['id', message['id']]) t.add_row(['initial_entry_time', message['initial_entry_time']]) t.add_row(['visibility_delay', message['visibility_delay']]) t.add_row(['visibility_interval', message['visibility_interval']]) t.add_row(['fields', message['fields']]) return [t, message['body']] def topic_table(topic): t = Table(['property', 'value']) t.align['property'] = 'r' t.align['value'] = 'l' t.add_row(['name', topic['name']]) t.add_row(['tags', listing(topic['tags'] or [])]) return t def subscription_table(sub): t = Table(['property', 'value']) t.align['property'] = 'r' t.align['value'] = 'l' t.add_row(['id', sub['id']]) t.add_row(['endpoint_type', sub['endpoint_type']]) for k, v in sub['endpoint'].items(): t.add_row([k, v]) return t class QueueList(CLIRunnable): __doc__ = """ usage: sl messaging queue-list [options] List all queues on an account """ + COMMON_MESSAGING_ARGS action = 'queue-list' @classmethod def execute(cls, client, args): manager = MessagingManager(client) mq_client = manager.get_connection(args['']) queues = mq_client.get_queues()['items'] t = Table([ 'name', 'message_count', 'visible_message_count' ]) for queue in queues: t.add_row([ queue['name'], queue['message_count'], queue['visible_message_count'], ]) return t class QueueDetail(CLIRunnable): __doc__ = """ usage: sl messaging queue-detail [options] Detail a queue """ + COMMON_MESSAGING_ARGS action = 'queue-detail' @classmethod def execute(cls, client, args): manager = MessagingManager(client) mq_client = manager.get_connection(args['']) queue = mq_client.get_queue(args['']) return queue_table(queue) class QueueCreate(CLIRunnable): __doc__ = """ usage: sl messaging queue-add [options] Create a queue Options: --visibility_interval=SECONDS Time in seconds that messages will re-appear after being popped --expiration=SECONDS Time in seconds that messages will live --tags=TAGS Comma-separated list of tags """ + COMMON_MESSAGING_ARGS action = 'queue-add' @classmethod def execute(cls, client, args): manager = MessagingManager(client) mq_client = manager.get_connection(args['']) tags = None if args.get('--tags'): tags = [tag.strip() for tag in args.get('--tags').split(',')] queue = mq_client.create_queue( args[''], visibility_interval=int(args.get('--visibility_interval') or 30), expiration=int(args.get('--expiration') or 604800), tags=tags, ) return queue_table(queue) class QueueModify(CLIRunnable): __doc__ = """ usage: sl messaging queue-edit [options] Modify a queue Options: --visibility_interval=SECONDS Time in seconds that messages will re-appear after being popped --expiration=SECONDS Time in seconds that messages will live --tags=TAGS Comma-separated list of tags """ + COMMON_MESSAGING_ARGS action = 'queue-edit' @classmethod def execute(cls, client, args): manager = MessagingManager(client) mq_client = manager.get_connection(args['']) tags = None if args.get('--tags'): tags = [tag.strip() for tag in args.get('--tags').split(',')] queue = mq_client.create_queue( args[''], visibility_interval=int(args.get('--visibility_interval') or 30), expiration=int(args.get('--expiration') or 604800), tags=tags, ) return queue_table(queue) class QueueDelete(CLIRunnable): __doc__ = """ usage: sl messaging queue-remove [] [options] Delete a queue or a queued message Options: --force Flag to force the deletion of the queue even when there are messages """ + COMMON_MESSAGING_ARGS action = 'queue-remove' @classmethod def execute(cls, client, args): manager = MessagingManager(client) mq_client = manager.get_connection(args['']) if args['']: mq_client.delete_message(args[''], args['']) else: mq_client.delete_queue(args[''], args.get('--force')) class QueuePush(CLIRunnable): __doc__ = """ usage: sl messaging queue-push ( | [-]) [options] Push a message into a queue Options: --force Flag to force the deletion of the queue even when there are messages """ + COMMON_MESSAGING_ARGS action = 'queue-push' @classmethod def execute(cls, client, args): manager = MessagingManager(client) mq_client = manager.get_connection(args['']) body = '' if args[''] is not None: body = args[''] else: body = sys.stdin.read() return message_table( mq_client.push_queue_message(args[''], body)) class QueuePop(CLIRunnable): __doc__ = """ usage: sl messaging queue-pop [options] Pops a message from a queue Options: --count=NUM Count of messages to pop --delete-after Remove popped messages from the queue """ + COMMON_MESSAGING_ARGS action = 'queue-pop' @classmethod def execute(cls, client, args): manager = MessagingManager(client) mq_client = manager.get_connection(args['']) messages = mq_client.pop_message( args[''], args.get('--count') or 1) formatted_messages = [] for message in messages['items']: formatted_messages.append(message_table(message)) if args.get('--delete-after'): for message in messages['items']: mq_client.delete_message( args[''], message['id']) return formatted_messages class TopicList(CLIRunnable): __doc__ = """ usage: sl messaging topic-list [options] List all topics on an account """ + COMMON_MESSAGING_ARGS action = 'topic-list' @classmethod def execute(cls, client, args): manager = MessagingManager(client) mq_client = manager.get_connection(args['']) topics = mq_client.get_topics()['items'] t = Table(['name']) for topic in topics: t.add_row([topic['name']]) return t class TopicDetail(CLIRunnable): __doc__ = """ usage: sl messaging topic-detail [options] Detail a topic """ + COMMON_MESSAGING_ARGS action = 'topic-detail' @classmethod def execute(cls, client, args): manager = MessagingManager(client) mq_client = manager.get_connection(args['']) topic = mq_client.get_topic(args['']) subscriptions = mq_client.get_subscriptions(args['']) tables = [] for sub in subscriptions['items']: tables.append(subscription_table(sub)) return [topic_table(topic), tables] class TopicCreate(CLIRunnable): __doc__ = """ usage: sl messaging topic-add [options] Create a new topic """ + COMMON_MESSAGING_ARGS action = 'topic-add' @classmethod def execute(cls, client, args): manager = MessagingManager(client) mq_client = manager.get_connection(args['']) tags = None if args.get('--tags'): tags = [tag.strip() for tag in args.get('--tags').split(',')] topic = mq_client.create_topic( args[''], visibility_interval=int( args.get('--visibility_interval') or 30), expiration=int(args.get('--expiration') or 604800), tags=tags, ) return topic_table(topic) class TopicDelete(CLIRunnable): __doc__ = """ usage: sl messaging topic-remove [options] Delete a topic or subscription Options: --force Flag to force the deletion of the topic even when there are subscriptions """ + COMMON_MESSAGING_ARGS action = 'topic-remove' @classmethod def execute(cls, client, args): manager = MessagingManager(client) mq_client = manager.get_connection(args['']) mq_client.delete_topic(args[''], args.get('--force')) class TopicSubscribe(CLIRunnable): __doc__ = """ usage: sl messaging topic-subscribe [options] Create a subscription on a topic Options: --type=TYPE Type of endpoint, [Options: http, queue] --queue-name=NAME Queue name. Required if --type is queue --http-method=METHOD HTTP Method to use if --type is http --http-url=URL HTTP/HTTPS URL to use. Required if --type is http --http-body=BODY HTTP Body template to use if --type is http """ + COMMON_MESSAGING_ARGS action = 'topic-subscribe' @classmethod def execute(cls, client, args): manager = MessagingManager(client) mq_client = manager.get_connection(args['']) if args['--type'] == 'queue': subscription = mq_client.create_subscription( args[''], 'queue', queue_name=args['--queue-name'], ) elif args['--type'] == 'http': subscription = mq_client.create_subscription( args[''], 'http', method=args['--http-method'] or 'GET', url=args['--http-url'], body=args['--http-body'] ) else: raise ArgumentError( '--type should be either queue or http.') return subscription_table(subscription) class TopicUnsubscribe(CLIRunnable): __doc__ = """ usage: sl messaging topic-unsubscribe [options] Remove a subscription on a topic """ + COMMON_MESSAGING_ARGS action = 'topic-unsubscribe' @classmethod def execute(cls, client, args): manager = MessagingManager(client) mq_client = manager.get_connection(args['']) mq_client.delete_subscription( args[''], args['']) class TopicPush(CLIRunnable): __doc__ = """ usage: sl messaging topic-push ( | [-]) [options] Push a message into a topic """ + COMMON_MESSAGING_ARGS action = 'topic-push' @classmethod def execute(cls, client, args): manager = MessagingManager(client) mq_client = manager.get_connection(args['']) # the message body comes from the positional argument or stdin body = '' if args[''] is not None: body = args[''] else: body = sys.stdin.read() return message_table( mq_client.push_topic_message(args[''], body)) softlayer-api-python-client-3.0.1/SoftLayer/CLI/modules/metadata.py000066400000000000000000000126171222600344500252130ustar00rootroot00000000000000""" usage: sl metadata [] [...] [options] Find details about this machine. These commands only work on devices on the backend SoftLayer network. This allows for self-discovery for newly provisioned resources. The available commands are: backend_ip Primary backend ip address backend_mac Backend mac addresses datacenter Datacenter name datacenter_id Datacenter id fqdn Fully qualified domain name frontend_mac Frontend mac addresses hostname Hostname id Id ip Primary ip address network Details about either the public or private network provision_state Provision state tags Tags user_data User-defined data """ # :copyright: (c) 2013, SoftLayer Technologies, Inc. All rights reserved. # :license: MIT, see LICENSE for more details. from SoftLayer import MetadataManager from SoftLayer.CLI import CLIRunnable, KeyValueTable, listing, CLIAbort class BackendMacAddresses(CLIRunnable): """ usage: sl metadata backend_mac [options] List backend mac addresses """ action = 'backend_mac' @staticmethod def execute(client, args): return listing(MetadataManager().get('backend_mac'), separator=',') class Datacenter(CLIRunnable): """ usage: sl metadata datacenter [options] Get datacenter name """ action = 'datacenter' @staticmethod def execute(client, args): return MetadataManager().get('datacenter') class DatacenterId(CLIRunnable): """ usage: sl metadata datacenter_id [options] Get datacenter id """ action = 'datacenter_id' @staticmethod def execute(client, args): return MetadataManager().get('datacenter_id') class FrontendMacAddresses(CLIRunnable): """ usage: sl metadata frontend_mac [options] List frontend mac addresses """ action = 'frontend_mac' @staticmethod def execute(client, args): return listing(MetadataManager().get('frontend_mac'), separator=',') class FullyQualifiedDomainName(CLIRunnable): """ usage: sl metadata fqdn [options] Get fully qualified domain name """ action = 'fqdn' @staticmethod def execute(client, args): return MetadataManager().get('fqdn') class Hostname(CLIRunnable): """ usage: sl metadata hostname [options] Get hostname """ action = 'hostname' @staticmethod def execute(client, args): return MetadataManager().get('hostname') class Id(CLIRunnable): """ usage: sl metadata id Get id """ action = 'id' @staticmethod def execute(client, args): return MetadataManager().get('id') class PrimaryBackendIpAddress(CLIRunnable): """ usage: sl metadata backend_ip [options] Get primary backend ip address """ action = 'backend_ip' @staticmethod def execute(client, args): return MetadataManager().get('primary_backend_ip') class PrimaryIpAddress(CLIRunnable): """ usage: sl metadata ip [options] Get primary ip address """ action = 'ip' @staticmethod def execute(client, args): return MetadataManager().get('primary_ip') class ProvisionState(CLIRunnable): """ usage: sl metadata provision_state [options] Get provision state """ action = 'provision_state' @staticmethod def execute(client, args): return MetadataManager().get('provision_state') class Tags(CLIRunnable): """ usage: sl metadata tags [options] List tags """ action = 'tags' @staticmethod def execute(client, args): return listing(MetadataManager().get('tags'), separator=',') class UserMetadata(CLIRunnable): """ usage: sl metadata user_data [options] Get user-defined data """ action = 'user_data' @staticmethod def execute(client, args): userdata = MetadataManager().get('user_data') if userdata: return userdata else: raise CLIAbort("No user metadata.") class Network(CLIRunnable): """ usage: sl metadata network ( | ) [options] Get details about the public or private network """ """ details about either the public or private network """ action = 'network' @staticmethod def execute(client, args): meta = MetadataManager() if args['']: t = KeyValueTable(['Name', 'Value']) t.align['Name'] = 'r' t.align['Value'] = 'l' network = meta.public_network() t.add_row([ 'mac addresses', listing(network['mac_addresses'], separator=',')]) t.add_row([ 'router', network['router']]) t.add_row([ 'vlans', listing(network['vlans'], separator=',')]) t.add_row([ 'vlan ids', listing(network['vlan_ids'], separator=',')]) return t if args['']: t = KeyValueTable(['Name', 'Value']) t.align['Name'] = 'r' t.align['Value'] = 'l' network = meta.private_network() t.add_row([ 'mac addresses', listing(network['mac_addresses'], separator=',')]) t.add_row([ 'router', network['router']]) t.add_row([ 'vlans', listing(network['vlans'], separator=',')]) t.add_row([ 'vlan ids', listing(network['vlan_ids'], separator=',')]) return t softlayer-api-python-client-3.0.1/SoftLayer/CLI/modules/nas.py000066400000000000000000000024001222600344500242010ustar00rootroot00000000000000""" usage: sl nas [] [...] [options] Manage NAS accounts The available commands are: list List NAS accounts """ # :copyright: (c) 2013, SoftLayer Technologies, Inc. All rights reserved. # :license: MIT, see LICENSE for more details. from SoftLayer.CLI import CLIRunnable, Table, FormattedItem from SoftLayer.CLI.helpers import NestedDict, blank class ListNAS(CLIRunnable): """ usage: sl nas list [options] List NAS accounts Options: """ action = 'list' @staticmethod def execute(client, args): account = client['Account'] nas = account.getNasNetworkStorage( mask='eventCount,serviceResource[datacenter.name]') nas = [NestedDict(n) for n in nas] t = Table(['id', 'datacenter', 'size', 'username', 'password', 'server']) for n in nas: t.add_row([ n['id'], n['serviceResource']['datacenter'].get('name', blank()), FormattedItem( n.get('capacityGb', blank()), "%dGB" % n.get('capacityGb', 0)), n.get('username', blank()), n.get('password', blank()), n.get('serviceResourceBackendIpAddress', blank())]) return t softlayer-api-python-client-3.0.1/SoftLayer/CLI/modules/rwhois.py000066400000000000000000000062371222600344500247470ustar00rootroot00000000000000""" usage: sl rwhois [] [...] [options] Manage the RWhoIs information on the account. The available commands are: edit Edit the RWhois data on the account show Show the RWhois data on the account """ # :copyright: (c) 2013, SoftLayer Technologies, Inc. All rights reserved. # :license: MIT, see LICENSE for more details. from SoftLayer import NetworkManager from SoftLayer.CLI import CLIRunnable, KeyValueTable from SoftLayer.CLI.helpers import CLIAbort class RWhoisEdit(CLIRunnable): """ usage: sl rwhois edit [options] Updates the RWhois information on your account. Only the fields you specify will be changed. To clear a value, specify an empty string like: "" Options: --abuse=EMAIL Set the abuse email --address1=ADDR Update the address 1 field --address2=ADDR Update the address 2 field --city=CITY Set the city information --country=COUNTRY Set the country information. Use the two-letter abbreviation. --firstname=NAME Update the first name field --lastname=NAME Update the last name field --postal=CODE Set the postal code field --private Flags the address as a private residence. --public Flags the address as a public residence. --state=STATE Set the state information. Use the two-letter abbreviation. """ action = 'edit' @staticmethod def execute(client, args): mgr = NetworkManager(client) update = { 'abuse_email': args.get('--abuse'), 'address1': args.get('--address1'), 'address2': args.get('--address2'), 'city': args.get('--city'), 'country': args.get('--country'), 'first_name': args.get('--firstname'), 'last_name': args.get('--lastname'), 'postal_code': args.get('--postal'), 'state': args.get('--state') } if args.get('--private'): update['private_residence'] = False elif args.get('--public'): update['private_residence'] = True check = [x for x in update.values() if x is not None] if not check: raise CLIAbort("You must specify at least one field to update.") mgr.edit_rwhois(**update) class RWhoisShow(CLIRunnable): """ usage: sl rwhois show [options] Display the RWhois information for your account. """ action = 'show' @staticmethod def execute(client, args): mgr = NetworkManager(client) result = mgr.get_rwhois() t = KeyValueTable(['Name', 'Value']) t.align['Name'] = 'r' t.align['Value'] = 'l' t.add_row(['Name', result['firstName'] + ' ' + result['lastName']]) t.add_row(['Company', result['companyName']]) t.add_row(['Abuse Email', result['abuseEmail']]) t.add_row(['Address 1', result['address1']]) if result.get('address2'): t.add_row(['Address 2', result['address2']]) t.add_row(['City', result['city']]) t.add_row(['State', result.get('state', '-')]) t.add_row(['Postal Code', result.get('postalCode', '-')]) t.add_row(['Country', result['country']]) return t softlayer-api-python-client-3.0.1/SoftLayer/CLI/modules/server.py000066400000000000000000000776731222600344500247560ustar00rootroot00000000000000""" usage: sl server [] [...] [options] sl server [-h | --help] Manage hardware servers The available commands are: cancel Cancel a dedicated server. cancel-reasons Provides the list of possible cancellation reasons create Create a new dedicated server create-options Display a list of creation options for a specific chassis detail Retrieve hardware details list List hardware devices list-chassis Provide a list of all chassis available for ordering nic-edit Edit NIC settings power-cycle Issues power cycle to server power-off Powers off a running server power-on Boots up a server reboot Reboots a running server reload Perform an OS reload For several commands, will be asked for. This can be the id, hostname or the ip address for a piece of hardware. """ # :copyright: (c) 2013, SoftLayer Technologies, Inc. All rights reserved. # :license: MIT, see LICENSE for more details. import re import os from os import linesep from SoftLayer.CLI.helpers import ( CLIRunnable, Table, KeyValueTable, FormattedItem, NestedDict, CLIAbort, blank, listing, gb, active_txn, no_going_back, resolve_id, confirm, ArgumentError, update_with_template_args, export_to_template) from SoftLayer import HardwareManager, SshKeyManager class ListServers(CLIRunnable): """ usage: sl server list [options] List hardware servers on the acount Examples: sl server list --datacenter=dal05 sl server list --network=100 --domain=example.com sl server list --tags=production,db Options: --sortby=ARG Column to sort by. options: id, datacenter, host, cores, memory, primary_ip, backend_ip Filters: -c, --cpu=CPU Number of CPU cores -D, --domain=DOMAIN Domain portion of the FQDN. example: example.com -d, --datacenter=DC Datacenter shortname (sng01, dal05, ...) -H, --hostname=HOST Host portion of the FQDN. example: server -m, --memory=MEMORY Memory in gigabytes -n, --network=MBPS Network port speed in Mbps --tags=ARG Only show instances that have one of these tags. Comma-separated. (production,db) For more on filters see 'sl help filters' """ action = 'list' @staticmethod def execute(client, args): manager = HardwareManager(client) tags = None if args.get('--tags'): tags = [tag.strip() for tag in args.get('--tags').split(',')] servers = manager.list_hardware( hostname=args.get('--hostname'), domain=args.get('--domain'), cpus=args.get('--cpu'), memory=args.get('--memory'), datacenter=args.get('--datacenter'), nic_speed=args.get('--network'), tags=tags) t = Table([ 'id', 'datacenter', 'host', 'cores', 'memory', 'primary_ip', 'backend_ip', 'active_transaction' ]) t.sortby = args.get('--sortby') or 'host' for server in servers: server = NestedDict(server) t.add_row([ server['id'], server['datacenter']['name'], server['fullyQualifiedDomainName'], server['processorPhysicalCoreAmount'], gb(server['memoryCapacity']), server['primaryIpAddress'] or blank(), server['primaryBackendIpAddress'] or blank(), active_txn(server), ]) return t class ServerDetails(CLIRunnable): """ usage: sl server detail [--passwords] [--price] [options] Get details for a hardware device Options: --passwords Show passwords (check over your shoulder!) --price Show associated prices """ action = 'detail' @staticmethod def execute(client, args): hardware = HardwareManager(client) t = KeyValueTable(['Name', 'Value']) t.align['Name'] = 'r' t.align['Value'] = 'l' hardware_id = resolve_id( hardware.resolve_ids, args.get(''), 'hardware') result = hardware.get_hardware(hardware_id) result = NestedDict(result) t.add_row(['id', result['id']]) t.add_row(['hostname', result['fullyQualifiedDomainName']]) t.add_row(['status', result['hardwareStatus']['status']]) t.add_row(['datacenter', result['datacenter']['name']]) t.add_row(['cores', result['processorPhysicalCoreAmount']]) t.add_row(['memory', gb(result['memoryCapacity'])]) t.add_row(['public_ip', result['primaryIpAddress'] or blank()]) t.add_row( ['private_ip', result['primaryBackendIpAddress'] or blank()]) t.add_row([ 'os', FormattedItem( result['operatingSystem']['softwareLicense'] ['softwareDescription']['referenceCode'] or blank(), result['operatingSystem']['softwareLicense'] ['softwareDescription']['name'] or blank() )]) t.add_row(['created', result['provisionDate'] or blank()]) vlan_table = Table(['type', 'number', 'id']) for vlan in result['networkVlans']: vlan_table.add_row([ vlan['networkSpace'], vlan['vlanNumber'], vlan['id']]) t.add_row(['vlans', vlan_table]) if result.get('notes'): t.add_row(['notes', result['notes']]) if args.get('--price'): t.add_row(['price rate', result['billingItem']['recurringFee']]) if args.get('--passwords'): user_strs = [] for item in result['operatingSystem']['passwords']: user_strs.append( "%s %s" % (item['username'], item['password'])) t.add_row(['users', listing(user_strs)]) tag_row = [] for tag in result['tagReferences']: tag_row.append(tag['tag']['name']) if tag_row: t.add_row(['tags', listing(tag_row, separator=',')]) if not result['privateNetworkOnlyFlag']: ptr_domains = client['Hardware_Server'].getReverseDomainRecords( id=hardware_id) for ptr_domain in ptr_domains: for ptr in ptr_domain['resourceRecords']: t.add_row(['ptr', ptr['data']]) return t class ServerReload(CLIRunnable): """ usage: sl server reload [--key=KEY...] [options] Reload the OS on a hardware server based on its current configuration Optional: -i, --postinstall=URI Post-install script to download (Only HTTPS executes, HTTP leaves file in /root) -k, --key=KEY SSH keys to add to the root user. Can be specified multiple times """ action = 'reload' options = ['confirm'] @staticmethod def execute(client, args): hardware = HardwareManager(client) hardware_id = resolve_id( hardware.resolve_ids, args.get(''), 'hardware') keys = [] if args.get('--key'): for key in args.get('--key'): key_id = resolve_id(SshKeyManager(client).resolve_ids, key, 'SshKey') keys.append(key_id) if args['--really'] or no_going_back(hardware_id): hardware.reload(hardware_id, args['--postinstall'], keys) else: CLIAbort('Aborted') class CancelServer(CLIRunnable): """ usage: sl server cancel [options] Cancel a dedicated server Options: --comment=COMMENT An optional comment to add to the cancellation ticket --reason=REASON An optional cancellation reason. See cancel-reasons for a list of available options """ action = 'cancel' options = ['confirm'] @classmethod def execute(cls, client, args): hw = HardwareManager(client) hw_id = resolve_id( hw.resolve_ids, args.get(''), 'hardware') comment = args.get('--comment') if not comment and not args['--really']: comment = cls.env.input("(Optional) Add a cancellation comment:") reason = args.get('--reason') if args['--really'] or no_going_back(hw_id): hw.cancel_hardware(hw_id, reason, comment) else: CLIAbort('Aborted') class ServerCancelReasons(CLIRunnable): """ usage: sl server cancel-reasons Display a list of cancellation reasons """ action = 'cancel-reasons' @staticmethod def execute(client, args): t = Table(['Code', 'Reason']) t.align['Code'] = 'r' t.align['Reason'] = 'l' mgr = HardwareManager(client) reasons = mgr.get_cancellation_reasons().iteritems() for code, reason in reasons: t.add_row([code, reason]) return t class ServerPowerOff(CLIRunnable): """ usage: sl server power-off [options] Power off an active server """ action = 'power-off' options = ['confirm'] @classmethod def execute(cls, client, args): hw = client['Hardware_Server'] mgr = HardwareManager(client) hw_id = resolve_id(mgr.resolve_ids, args.get(''), 'hardware') if args['--really'] or confirm('This will power off the server with ' 'id %s. Continue?' % hw_id): hw.powerOff(id=hw_id) else: raise CLIAbort('Aborted.') class ServerReboot(CLIRunnable): """ usage: sl server reboot [--hard | --soft] [options] Reboot an active server Optional: --hard Perform an abrupt reboot --soft Perform a graceful reboot """ action = 'reboot' options = ['confirm'] @classmethod def execute(cls, client, args): hw = client['Hardware_Server'] mgr = HardwareManager(client) hw_id = resolve_id(mgr.resolve_ids, args.get(''), 'hardware') if args['--really'] or confirm('This will power off the server with ' 'id %s. Continue?' % hw_id): if args['--hard']: hw.rebootHard(id=hw_id) elif args['--soft']: hw.rebootSoft(id=hw_id) else: hw.rebootDefault(id=hw_id) else: raise CLIAbort('Aborted.') class ServerPowerOn(CLIRunnable): """ usage: sl server power-on [options] Power on a server """ action = 'power-on' @classmethod def execute(cls, client, args): hw = client['Hardware_Server'] mgr = HardwareManager(client) hw_id = resolve_id(mgr.resolve_ids, args.get(''), 'hardware') hw.powerOn(id=hw_id) class ServerPowerCycle(CLIRunnable): """ usage: sl server power-cycle [options] Issues power cycle to server via the power strip """ action = 'power-cycle' options = ['confirm'] @classmethod def execute(cls, client, args): hw = client['Hardware_Server'] mgr = HardwareManager(client) hw_id = resolve_id(mgr.resolve_ids, args.get(''), 'hardware') if args['--really'] or confirm('This will power off the server with ' 'id %s. Continue?' % hw_id): hw.powerCycle(id=hw_id) else: raise CLIAbort('Aborted.') class NicEditServer(CLIRunnable): """ usage: sl server nic-edit (public | private) --speed=SPEED [options] Manage NIC settings Options: --speed=SPEED Port speed. 0 disables the port. [Options: 0, 10, 100, 1000, 10000] """ action = 'nic-edit' @classmethod def execute(cls, client, args): public = args['public'] mgr = HardwareManager(client) hw_id = resolve_id(mgr.resolve_ids, args.get(''), 'hardware') mgr.change_port_speed(hw_id, public, args['--speed']) class ListChassisServer(CLIRunnable): """ usage: sl server list-chassis [options] Display a list of chassis available for ordering dedicated servers. """ action = 'list-chassis' @staticmethod def execute(client, args): t = Table(['Code', 'Chassis']) t.align['Code'] = 'r' t.align['Chassis'] = 'l' mgr = HardwareManager(client) chassis = mgr.get_available_dedicated_server_packages() for chassis in chassis: t.add_row([chassis[0], chassis[1]]) return t class ServerCreateOptions(CLIRunnable): """ usage: sl server create-options [options] Output available available options when creating a dedicated server with the specified chassis. Options: --all Show all options. default if no other option provided --controller Show disk controller options --cpu Show CPU options --datacenter Show datacenter options --disk Show disk options --memory Show memory size options --nic Show NIC speed options --os Show operating system options """ action = 'create-options' options = ['datacenter', 'cpu', 'memory', 'os', 'disk', 'nic', 'controller'] @classmethod def execute(cls, client, args): mgr = HardwareManager(client) t = KeyValueTable(['Name', 'Value']) t.align['Name'] = 'r' t.align['Value'] = 'l' chassis_id = args.get('') ds_options = mgr.get_dedicated_server_create_options(chassis_id) show_all = True for opt_name in cls.options: if args.get("--" + opt_name): show_all = False break if args['--all']: show_all = True if args['--datacenter'] or show_all: results = cls.get_create_options(ds_options, 'datacenter')[0] t.add_row([results[0], listing(sorted(results[1]))]) if args['--cpu'] or show_all: results = cls.get_create_options(ds_options, 'cpu') cpu_table = Table(['id', 'description']) for result in sorted(results): cpu_table.add_row([result[1], result[0]]) t.add_row(['cpu', cpu_table]) if args['--memory'] or show_all: results = cls.get_create_options(ds_options, 'memory')[0] t.add_row([results[0], listing( item[0] for item in sorted(results[1]))]) if args['--os'] or show_all: results = cls.get_create_options(ds_options, 'os') for result in results: t.add_row([ result[0], listing( [item[0] for item in sorted(result[1])], separator=linesep )]) if args['--disk'] or show_all: results = cls.get_create_options(ds_options, 'disk')[0] t.add_row([ results[0], listing( [item[0] for item in sorted(results[1])], separator=linesep )]) if args['--nic'] or show_all: results = cls.get_create_options(ds_options, 'nic') for result in results: t.add_row([result[0], listing( item[0] for item in sorted(result[1],))]) if args['--controller'] or show_all: results = cls.get_create_options(ds_options, 'disk_controller')[0] t.add_row([results[0], listing( item[0] for item in sorted(results[1],))]) return t @classmethod def get_create_options(cls, ds_options, section, pretty=True): """ This method can be used to parse the bare metal instance creation options into different sections. This can be useful for data validation as well as printing the options on a help screen. :param dict ds_options: The instance options to parse. Must come from the .get_bare_metal_create_options() function in the HardwareManager. :param string section: The section to parse out. :param bool pretty: If true, it will return the results in a 'pretty' format that's easier to print. """ if 'datacenter' == section: datacenters = [loc['keyname'] for loc in ds_options['locations']] return [('datacenter', datacenters)] elif 'cpu' == section: results = [] for item in ds_options['categories']['server']['items']: results.append(( item['description'], item['price_id'] )) return results elif 'memory' == section: ram = [] for option in ds_options['categories']['ram']['items']: ram.append((int(option['capacity']), option['price_id'])) return [('memory', ram)] elif 'os' == section: os_regex = re.compile('(^[A-Za-z\s\/\-]+) ([\d\.]+)') bit_regex = re.compile(' \((\d+)\s*bit') extra_regex = re.compile(' - (.+)\(') # Encapsulate the code for generating the operating system code def _generate_os_code(name, version, bits, extra_info): name = name.replace(' Linux', '') name = name.replace('Enterprise', '') name = name.replace('GNU/Linux', '') os_code = name.strip().replace(' ', '_').upper() if os_code.startswith('RED_HAT'): os_code = 'REDHAT' if 'UBUNTU' in os_code: version = re.sub('\.\d+', '', version) os_code += '_' + version.replace('.0', '') if bits: os_code += '_' + bits if extra_info: garbage = ['Install', '(32 bit)', '(64 bit)'] for g in garbage: extra_info = extra_info.replace(g, '') os_code += '_' + \ extra_info.strip().replace(' ', '_').upper() return os_code # Also separate out the code for generating the Windows OS code # since it's significantly different from the rest. def _generate_windows_code(description): version_check = re.search('Windows Server (\d+)', description) version = version_check.group(1) os_code = 'WIN_' + version if 'Datacenter' in description: os_code += '-DC' elif 'Enterprise' in description: os_code += '-ENT' else: os_code += '-STD' if 'ith R2' in description: os_code += '-R2' elif 'ith Hyper-V' in description: os_code += '-HYPERV' bit_check = re.search('\((\d+)\s*bit', description) if bit_check: os_code += '_' + bit_check.group(1) return os_code # Loop through the operating systems and get their OS codes os_list = {} flat_list = [] for os in ds_options['categories']['os']['items']: if 'Windows Server' in os['description']: os_code = _generate_windows_code(os['description']) else: os_results = os_regex.search(os['description']) name = os_results.group(1) version = os_results.group(2) bits = bit_regex.search(os['description']) extra_info = extra_regex.search(os['description']) if bits: bits = bits.group(1) if extra_info: extra_info = extra_info.group(1) os_code = _generate_os_code(name, version, bits, extra_info) name = os_code.split('_')[0] if name not in os_list: os_list[name] = [] os_list[name].append((os_code, os['price_id'])) flat_list.append((os_code, os['price_id'])) if pretty: results = [] for os in sorted(os_list.keys()): results.append(('os (%s)' % os, os_list[os])) return results else: return [('os', flat_list)] elif 'disk' == section: disks = [] type_regex = re.compile('^[\d\.]+[GT]B\s+(.+)$') for disk in ds_options['categories']['disk0']['items']: disk_type = 'SATA' disk_type = type_regex.match(disk['description']).group(1) disk_type = disk_type.replace('RPM', '').strip() disk_type = disk_type.replace(' ', '_').upper() disk_type = str(int(disk['capacity'])) + '_' + disk_type disks.append((disk_type, disk['price_id'], disk['id'])) return [('disk', disks)] elif 'nic' == section: single = [] dual = [] for item in ds_options['categories']['port_speed']['items']: if 'dual' in item['description'].lower(): dual.append((str(int(item['capacity'])) + '_DUAL', item['price_id'])) else: single.append((str(int(item['capacity'])), item['price_id'])) return [('single nic', single), ('dual nic', dual)] elif 'disk_controller' == section: options = [] for item in ds_options['categories']['disk_controller']['items']: text = item['description'].replace(' ', '') if 'Non-RAID' == text: text = 'None' options.append((text, item['price_id'])) return [('disk_controllers', options)] class CreateServer(CLIRunnable): """ usage: sl server create [--disk=SIZE...] [--key=KEY...] [options] Order/create a dedicated server. See 'sl server list-chassis' and 'sl server create-options' for valid options. --disk can be repeated to order multiple disks. Required: -H --hostname=HOST Host portion of the FQDN. example: server -D --domain=DOMAIN Domain portion of the FQDN. example: example.com --chassis=CHASSIS The chassis to use for the new server -c --cpu=CPU CPU model -o OS, --os=OS OS install code. -m --memory=MEMORY Memory in gigabytes. example: 4 Optional: -d, --datacenter=DC datacenter name Note: Omitting this value defaults to the first available datacenter -n, --network=MBPS Network port speed in Mbps -d, --disk=SIZE... Disks. Can be specified multiple times --controller=RAID The RAID configuration for the server. Defaults to None. -k KEY, --key=KEY SSH keys to assign to the root user. Can be specified multiple times. --dry-run, --test Do not create the server, just get a quote --vlan_public=VLAN The ID of the public VLAN on which you want the CCI placed. --vlan_private=VLAN The ID of the private VLAN on which you want the CCI placed. -t, --template=FILE A template file that defaults the command-line options using the long name in INI format --export=FILE Exports options to a template file """ action = 'create' options = ['confirm'] required_params = ['--hostname', '--domain', '--chassis', '--cpu', '--memory', '--os'] @classmethod def execute(cls, client, args): update_with_template_args(args) mgr = HardwareManager(client) # Disks will be a comma-separated list. Let's make it a real list. if isinstance(args.get('--disk'), str): args['--disk'] = args.get('--disk').split(',') # Do the same thing for SSH keys if isinstance(args.get('--key'), str): args['--key'] = args.get('--key').split(',') cls._validate_args(args) ds_options = mgr.get_dedicated_server_create_options(args['--chassis']) order = { 'hostname': args['--hostname'], 'domain': args['--domain'], 'bare_metal': False, 'package_id': args['--chassis'], } # Convert the OS code back into a price ID os_price = cls._get_price_id_from_options(ds_options, 'os', args['--os']) if os_price: order['os'] = os_price else: raise CLIAbort('Invalid operating system specified.') order['location'] = args['--datacenter'] or 'FIRST_AVAILABLE' order['server'] = args['--cpu'] order['ram'] = cls._get_price_id_from_options(ds_options, 'memory', int(args['--memory'])) # Set the disk sizes disk_prices = [] disk_number = 0 for disk in args.get('--disk'): disk_price = cls._get_disk_price(ds_options, disk, disk_number) disk_number += 1 if disk_price: disk_prices.append(disk_price) if not disk_prices: disk_prices.append(cls._get_default_value(ds_options, 'disk0')) order['disks'] = disk_prices # Set the disk controller price if args.get('--controller'): dc_price = cls._get_price_id_from_options(ds_options, 'disk_controller', args.get('--controller')) else: dc_price = cls._get_price_id_from_options(ds_options, 'disk_controller', 'None') order['disk_controller'] = dc_price # Set the port speed port_speed = args.get('--network') or '100' nic_price = cls._get_price_id_from_options(ds_options, 'nic', port_speed) if nic_price: order['port_speed'] = nic_price else: raise CLIAbort('Invalid NIC speed specified.') # Get the SSH keys if args.get('--key'): keys = [] for key in args.get('--key'): key_id = resolve_id(SshKeyManager(client).resolve_ids, key, 'SshKey') keys.append(key_id) order['ssh_keys'] = keys if args.get('--vlan_public'): order['public_vlan'] = args['--vlan_public'] if args.get('--vlan_private'): order['private_vlan'] = args['--vlan_private'] # Do not create hardware server with --test or --export do_create = not (args['--export'] or args['--test']) output = None if args.get('--test'): result = mgr.verify_order(**order) t = Table(['Item', 'cost']) t.align['Item'] = 'r' t.align['cost'] = 'r' total = 0.0 for price in result['prices']: total += float(price.get('recurringFee', 0.0)) rate = "%.2f" % float(price['recurringFee']) t.add_row([price['item']['description'], rate]) t.add_row(['Total monthly cost', "%.2f" % total]) output = [] output.append(t) output.append(FormattedItem( '', ' -- ! Prices reflected here are retail and do not ' 'take account level discounts and are not guaranteed.') ) if args['--export']: export_file = args.pop('--export') export_to_template(export_file, args, exclude=['--wait', '--test']) return 'Successfully exported options to a template file.' if do_create: if args['--really'] or confirm( "This action will incur charges on your account. " "Continue?"): result = mgr.place_order(**order) t = KeyValueTable(['name', 'value']) t.align['name'] = 'r' t.align['value'] = 'l' t.add_row(['id', result['orderId']]) t.add_row(['created', result['orderDate']]) output = t else: raise CLIAbort('Aborting dedicated server order.') return output @classmethod def _validate_args(cls, args): invalid_args = [k for k in cls.required_params if args.get(k) is None] if invalid_args: raise ArgumentError('Missing required options: %s' % ','.join(invalid_args)) @classmethod def _get_default_value(cls, ds_options, option): if option not in ds_options['categories']: return for item in ds_options['categories'][option]['items']: if not any([ float(item.get('setupFee', 0)), float(item.get('recurringFee', 0)), float(item.get('hourlyRecurringFee', 0)), float(item.get('oneTimeFee', 0)), float(item.get('laborFee', 0)), ]): return item['price_id'] @classmethod def _get_disk_price(cls, ds_options, value, number): if not number: return cls._get_price_id_from_options(ds_options, 'disk', value) # This will get the item ID for the matching identifier string, which # we can then use to get the price ID for our specific disk item_id = cls._get_price_id_from_options(ds_options, 'disk', value, True) key = 'disk' + str(number) if key in ds_options['categories']: for item in ds_options['categories'][key]['items']: if item['id'] == item_id: return item['price_id'] @classmethod def _get_price_id_from_options(cls, ds_options, option, value, item_id=False): ds_obj = ServerCreateOptions() for k, v in ds_obj.get_create_options(ds_options, option, False): for item_options in v: if item_options[0] == value: if not item_id: return item_options[1] return item_options[2] class EditServer(CLIRunnable): """ usage: sl server edit [options] Edit hardware details Options: -D --domain=DOMAIN Domain portion of the FQDN example: example.com -F --userfile=FILE Read userdata from file -H --hostname=HOST Host portion of the FQDN. example: server -u --userdata=DATA User defined metadata string """ action = 'edit' @staticmethod def execute(client, args): data = {} if args['--userdata'] and args['--userfile']: raise ArgumentError('[-u | --userdata] not allowed with ' '[-F | --userfile]') if args['--userfile']: if not os.path.exists(args['--userfile']): raise ArgumentError( 'File does not exist [-u | --userfile] = %s' % args['--userfile']) if args.get('--userdata'): data['userdata'] = args['--userdata'] elif args.get('--userfile'): f = open(args['--userfile'], 'r') try: data['userdata'] = f.read() finally: f.close() data['hostname'] = args.get('--hostname') data['domain'] = args.get('--domain') hw = HardwareManager(client) hw_id = resolve_id(hw.resolve_ids, args.get(''), 'hardware') if not hw.edit(hw_id, **data): raise CLIAbort("Failed to update hardware") softlayer-api-python-client-3.0.1/SoftLayer/CLI/modules/sshkey.py000066400000000000000000000110301222600344500247250ustar00rootroot00000000000000""" usage: sl sshkey [] [...] [options] Manage SSH keys The available commands are: add Add a new SSH key to your account remove Removes an SSH key edit Edits information about the SSH key list Display a list of SSH keys on your account print Prints out an SSH key """ # :copyright: (c) 2013, SoftLayer Technologies, Inc. All rights reserved. # :license: MIT, see LICENSE for more details. from os.path import expanduser from SoftLayer import SshKeyManager from SoftLayer.CLI import CLIRunnable, Table, no_going_back from SoftLayer.CLI.helpers import CLIAbort, resolve_id, KeyValueTable class AddSshKey(CLIRunnable): """ usage: sl sshkey add