class Resource(resource.Resource): """Workloads API methods""" @command(parser_options=dict(description=LIST_DESCRIPTION)) @display_table(Workload) def list(self): """ Get list of Lava workloads :returns: List of :class:`~lavaclient.api.response.Workload` objects """ return self._parse_response(self._client._get('/workloads'), WorkloadsResponse, wrapper='workloads') @command(parser_options=dict( description='Get recommendations for a certain workload', epilog=RECOMMENDATIONS_EPILOG, ), storage_size=argument(type=int, help='Required disk space (in GB)'), persistence=argument(choices=['all', 'data', 'none'], help='See PERSISTENCE')) @display(display_recommendations) def recommendations(self, workload_id, storage_size, persistence): """ Get recommendations for the given workload. :param workload_id: Workload ID (see the :func:`get` method) :param storage_size: The desired amount of disk space (in GB) :param persistence: One of the following values: - 'data': persistent (large) data storage is required - 'all': persist data and your cluster servers - 'none': your data and clusters are ephemeral :returns: List of :class:`~lavaclient.api.response.Recommendations` objects """ params = self._marshal_request( { 'storagesize': storage_size, 'persistent': persistence }, RecommendationParams) return self._parse_response(self._client._get( '/workloads/{0}/recommendations'.format(workload_id), params=params), RecommendationsResponse, wrapper='recommendations')
class Resource(resource.Resource): """ Clusters API methods """ @command( parser_options=dict(description='List all existing clusters', ), ) @display_table(Cluster) def list(self): """ List clusters that belong to the tenant specified in the client :returns: List of :class:`~lavaclient.api.response.Cluster` objects """ return self._parse_response(self._client._get('clusters'), ClustersResponse, wrapper='clusters') @command( parser_options=dict( description='Display an existing cluster in detail', ), ) @display_table(ClusterDetail) def get(self, cluster_id): """ Get the cluster corresponding to the cluster ID :param cluster_id: Cluster ID :returns: :class:`~lavaclient.api.response.ClusterDetail` """ return self._parse_response( self._client._get('clusters/' + six.text_type(cluster_id)), ClusterResponse, wrapper='cluster') def _gather_node_groups(self, node_groups): """Transform node_groups into a list of dicts""" if isinstance(node_groups, dict): node_group_list = [] for key, node_group in six.iteritems(node_groups): node_group.update(id=key) node_group_list.append(node_group) return node_group_list else: return node_groups def create(self, name, stack_id, username=None, ssh_keys=None, user_scripts=None, node_groups=None, connectors=None, wait=False, credentials=None): """ Create a cluster :param name: Cluster name :param stack_id: Valid stack identifier :param username: User to create on the cluster; defaults to local user :param ssh_keys: List of SSH keys; if none is specified, it will use the key `user@hostname`, creating the key from `$HOME/.ssh/id_rsa.pub` if it doesn't exist. :param node_groups: `dict` of `(node_group_id, attrs)` pairs, in which `attrs` is a `dict` of node group attributes. Instead of a `dict`, you may give a `list` of `dicts`, each containing the `id` key. Currently supported attributes are `flavor_id` and `count` :param user_scripts: List of user script ID's; See :meth:`lavaclient.api.scripts.Resource.create` :param credentials: List of credentials to use. Each item must be a dictionary of `(type, name)` pairs :param connectors: List of connector credentials to use. Each item must be a dictionary of `(type, name)` pairs. Deprecated in favor of `credentials` :param wait: If `True`, wait for the cluster to become active before returning :returns: :class:`~lavaclient.api.response.ClusterDetail` """ if ssh_keys is None: ssh_keys = [DEFAULT_SSH_KEY] if username is None: username = getuser() if connectors is None: connectors = [] if credentials is None: credentials = [] if connectors: deprecation('connectors are deprecated; use credentials') data = dict(name=name, username=username, ssh_keys=ssh_keys, stack_id=stack_id) if node_groups: data.update(node_groups=self._gather_node_groups(node_groups)) if user_scripts: data.update(scripts=[{'id': script} for script in user_scripts]) if connectors or credentials: cdata = [] for credential in connectors + credentials: ctype, name = six.next(six.iteritems(credential)) cdata.append({'type': ctype, 'credential': {'name': name}}) data.update(credentials=cdata) request_data = self._marshal_request(data, ClusterCreateRequest, wrapper='cluster') cluster = self._parse_response(self._client._post('clusters', json=request_data), ClusterResponse, wrapper='cluster') if wait: return self.wait(cluster.id) return cluster @command( parser_options=dict(description='Resize an existing Lava cluster', ), node_groups=argument( type=parse_node_group, action='append', help='Node group options; may be used multiple times to resize ' 'multiple node groups. Each option should be in the form ' '\'<id>(count=<value>)\', where <id> is a valid node ' 'group ID for the cluster and the count is the ' 'option to specify new count for that node group. '), wait=argument(action='store_true', help='Wait for the cluster to become active'), ) @display_table(ClusterDetail) def resize(self, cluster_id, node_groups=None, wait=False): """ Resize a cluster :param cluster_id: Cluster ID :param node_groups: `dict` of `(node_group_id, attrs)` pairs, in which `attrs` is a `dict` of node group attributes. Instead of a `dict`, you may give a `list` of `dicts`, each containing the `id` key. Currently supported attributes are `flavor_id` and `count` :returns: :class:`~lavaclient.api.response.ClusterDetail` """ if not node_groups: raise error.RequestError("Must specify atleast one node_group " "to resize") gathered = self._gather_node_groups(node_groups) if not all('count' in node_group and 'id' in node_group for node_group in gathered): raise error.RequestError("Invalid or missing option " "in the node groups") data = dict(cluster=dict(node_groups=gathered)) request_data = self._marshal_request(data, ClusterUpdateRequest) cluster = self._parse_response(self._client._put( 'clusters/{0}'.format(cluster_id), json=request_data), ClusterResponse, wrapper='cluster') if wait: return self.wait(cluster.id) return cluster @command( parser_options=dict( description='Update the connector credentials for a cluster', ), credentials=argument( '--credential', type=parse_credential, action='append', help='Credentials to use in the cluster. Only the credentials you ' 'specify are modified. No credentials are removed. Each must be' ' in the form of `type=name`. See `lava credentials`'), wait=argument(action='store_true', help='Wait for the cluster to become active')) @display_table(ClusterDetail) def _update_credentials(self, cluster_id, credentials=None, wait=False): if not credentials: raise error.RequestError("Must specify at least one credential") creds = defaultdict(list) for cred in credentials: ctype, name = six.next(c for c in six.iteritems(cred)) creds[ctype].append(name) return self.update_credentials(cluster_id, creds, wait) def update_credentials(self, cluster_id, credentials=None, wait=False): """ Update the credentials on a cluster :param cluster_id: Cluster ID :param credentials: The credential name to on update the cluster. Credentials must be a dict of {type: [name]} values :param wait: If `True`, wait for the cluster to become active before returning :returns: :class:`~lavaclient.api.response.ClusterDetail` """ new_creds = [] for ctype in credentials: names = credentials[ctype] for name in names: new_creds.append({'name': name, 'type': ctype}) request_data = self._marshal_request({'credentials': new_creds}, ClusterCredentialsRequest, wrapper='cluster') response = self._client._put('clusters/{0}'.format(cluster_id), json=request_data) cluster = self._parse_response(response, ClusterResponse, wrapper='cluster') if wait: return self.wait(cluster.id) return cluster @command(parser_options=dict( description='Remove the specified ssh keys from a cluster cluster', ), keynames=argument( metavar='keyname', nargs='+', help='Name of ssh credential to remove from the cluster'), wait=argument(action='store_true', help='Wait for the cluster to become active'), force=argument(action='store_true', help="Suppress delete confirmation dialog")) @display_table(ClusterDetail) def _delete_ssh_credentials(self, cluster_id, keynames, wait=False, force=False): if not force: if not confirm('Remove these keys from this cluster?'): return return self.delete_ssh_credentials(cluster_id, keynames, wait) def delete_ssh_credentials(self, cluster_id, keynames, wait): """ Remove the specified SSH credentials from a cluster. :param cluster_id: Cluster ID :param keys: The ssh key names to remove from the cluster :param wait: If `True`, wait for the cluster to become active before returning :returns: :class:`~lavaclient.api.response.ClusterDetail` """ creds = [] for name in keynames: creds.append({'name': name, 'type': 'ssh_keys'}) request_data = self._marshal_request({'remove_credentials': creds}, ClusterCredentialsRemovalRequest, wrapper='cluster') response = self._client._put('clusters/{0}'.format(cluster_id), json=request_data) cluster = self._parse_response(response, ClusterResponse, wrapper='cluster') if wait: return self.wait(cluster.id) return cluster def _create_default_ssh_credential(self): if not confirm( 'You have not uploaded any SSH key credentials; do ' 'you want to upload {0} now?'.format(DEFAULT_SSH_PUBKEY)): sys.exit(1) try: with open(expand(DEFAULT_SSH_PUBKEY)) as f: six.print_('SSH key does not exist; creating...') self._client.credentials.create_ssh_key( DEFAULT_SSH_KEY, f.read().strip()) except IOError: six.print_('No SSH keypair found; to generate a keypair, run ' '`ssh-keygen`') sys.exit(1) return DEFAULT_SSH_KEY @command( parser_options=dict(description='Create a new Lava cluster', ), name=argument(help='Cluster name'), username=argument( help='Login name of the user to install onto the created nodes; ' 'defaults to {0}'.format(getuser()), default=getuser()), ssh_keys=argument( '--ssh-key', action='append', help="SSH key name; may be used multiple times. If not " "specified, the client will attempt to use the key " "'{key}', creating it from ~/.ssh/id_rsa.pub if it " "doesn't exist. See `lava credentials`".format( key=DEFAULT_SSH_KEY)), stack_id=argument( help='Valid Lava stack ID. For a list of stacks, use the ' '`lava stacks list` command'), user_scripts=argument( '--user-script', action='append', help='User script ID: See `lava scripts --help` for more ' 'information; may be used multiple times to provide multiple ' 'script IDs'), node_groups=argument( type=parse_node_group, action='append', help='Node group options; may be used multiple times to ' 'configure multiple node groups. Each option should be in ' 'the form <id>(<key>=<value>, ...), where <id> is a valid ' 'node group ID for the stack and the key-value pairs are ' 'options to specify for that node group. Current valid ' 'options are `count` and `flavor_id`'), credentials=argument( '--credential', action='append', type=parse_credential, help='Credentials to use in the cluster. Each must be in the ' 'form of `type=name`. See `lava credentials`'), connectors=argument('--connector', action='append', type=parse_credential, help='Deprecated'), wait=argument(action='store_true', help='Wait for the cluster to become active'), ) @display_table(ClusterDetail) def _create(self, name, stack_id, username=None, ssh_keys=None, user_scripts=None, node_groups=None, connectors=None, wait=False, credentials=None): """ CLI-only; cluster create command """ if ssh_keys is None: ssh_keys = [DEFAULT_SSH_KEY] try: return self.create(name, stack_id, username=username, ssh_keys=ssh_keys, user_scripts=user_scripts, node_groups=node_groups, connectors=connectors, wait=wait, credentials=credentials) except error.RequestError as exc: if self._args.headless or not ( ssh_keys == [DEFAULT_SSH_KEY] and ('Cannot find requested ssh_keys' in str(exc) or 'One or more ssh_keys are invalid' in str(exc))): raise # Create the SSH key for the user and then attempt to create the # cluster again self._create_default_ssh_credential() return self.create(name, stack_id, username=username, ssh_keys=ssh_keys, user_scripts=user_scripts, node_groups=node_groups, connectors=connectors, wait=wait, credentials=credentials) @command( parser_options=dict(description='Delete a cluster', ), force=argument(action='store_true', help="Suppress delete confirmation dialog"), ) def _delete(self, cluster_id, force=False): if not force: display_result(self.get(cluster_id), ClusterDetail) if not confirm('Delete this cluster?'): return self.delete(cluster_id) def delete(self, cluster_id): """ Delete a cluster :param cluster_id: Cluster ID :returns: :class:`~lavaclient.api.response.ClusterDetail` """ self._client._delete('clusters/' + six.text_type(cluster_id)) @coroutine def _cli_wait_printer(self, start): """Coroutine that runs during the wait command. Prints status to stdout if the command line is running; otherwise, just log the status.""" started = False while True: cluster = yield LOG.debug('Cluster {0}: {1}'.format(cluster.id, cluster.status)) if self._command_line: if not started: started = True cli_msg_length = 0 six.print_('Waiting for cluster {0}'.format(cluster.id)) msg = 'Status: {0} (Elapsed time: {1:.1f} minutes)'.format( cluster.status, elapsed_minutes(start)) sys.stdout.write('\b' * cli_msg_length + msg) sys.stdout.flush() cli_msg_length = len(msg) if cluster.status == 'ACTIVE': six.print_('\n') @command( parser_options=dict( description='Poll a cluster until it becomes active'), timeout=argument(type=natural_number, help='Poll timeout (in minutes)'), interval=argument(type=natural_number, help='Poll interval (in seconds)'), ) @display_table(ClusterDetail) def wait(self, cluster_id, timeout=None, interval=None): """ Wait (blocking) for a cluster to either become active or fail. :param cluster_id: Cluster ID :param timeout: Wait timeout in minutes (default: no timeout) :returns: :class:`~lavaclient.api.response.ClusterDetail` """ if interval is None: interval = WAIT_INTERVAL interval = max(MIN_INTERVAL, interval) delta = timedelta(minutes=timeout) if timeout else timedelta(days=365) start = datetime.now() timeout_date = start + delta printer = self._cli_wait_printer(start) while datetime.now() < timeout_date: cluster = self.get(cluster_id) printer.send(cluster) if cluster.status == 'ACTIVE': return cluster elif cluster.status not in IN_PROGRESS_STATES: raise error.FailedError('Cluster status is {0}'.format( cluster.status)) if datetime.now() + timedelta(seconds=interval) >= timeout_date: break time.sleep(interval) raise error.TimeoutError( 'Cluster did not become active before timeout') @command(parser_options=dict(description='List all nodes in the cluster')) @display(Node.display_nodes) def nodes(self, cluster_id): """ Get the cluster nodes :param cluster_id: Cluster ID :returns: List of :class:`~lavaclient.api.response.Node` objects """ return self._client.nodes.list(cluster_id) def _get_named_node(self, nodes, node_name=None, wait=False): if node_name is None: return nodes[0] try: return six.next(node for node in nodes if node.name.lower() == node_name.lower()) except StopIteration: raise error.InvalidError( 'Invalid node: {0}; available nodes are {1}'.format( node_name, ', '.join(node.name for node in nodes))) def _get_component_node(self, nodes, component, wait=False): components_by_node = ((node, (comp['name'].lower() for comp in node.components)) for node in nodes) try: return six.next(node for node, components in components_by_node if component.lower() in components) except StopIteration: raise error.InvalidError( 'Component {0} not found in cluster'.format(component)) def _ssh_cluster_nodes(self, cluster_id, wait=False): """ Return `(cluster, nodes)`, where `nodes` is a list of all non-Ambari nodes in the cluster. If the cluster is not ACTIVE/ERROR/IMPAIRED and wait is `True`, the function will block until it becomes active; otherwise, an exception is thrown. """ cluster = self.get(cluster_id) status = cluster.status.upper() if status not in FINAL_STATES: LOG.debug('Cluster status: %s', status) if status not in IN_PROGRESS_STATES: raise error.InvalidError( 'Cluster is in state {0}'.format(status)) elif not wait: raise error.InvalidError('Cluster is not yet active') self.wait(cluster_id) if cluster.status.upper() == 'ERROR': raise error.InvalidError('Cluster is in state ERROR') nodes = [ node for node in self.nodes(cluster_id) if node.name.lower() != 'ambari' ] return cluster, nodes @command( parser_options=dict( description='Create a SOCKS5 proxy over SSH to a node in the ' 'cluster'), port=argument(type=int, help='Port on localhost on which to create ' 'the proxy'), node_name=argument(help='Name of node on which to make the SSH ' 'connection, e.g. gateway-1. By default, use ' 'first available node.'), ssh_command=argument(help="SSH command (default: 'ssh')"), wait=argument(action='store_true', help="Wait for cluster to become active (if it isn't " "already)"), ) def ssh_proxy(self, cluster_id, port=None, node_name=None, ssh_command=None, wait=False): """ Set up a SOCKS5 proxy over SSH to a node in the cluster. Returns the SSH process (via :py:class:`~subprocess.Popen`), which can be stopped via the :func:`kill` method. :param cluster_id: Cluster ID :param port: Local port on which to create the proxy (default: 12345) :param node_name: Name of node on which to make the SSH connection. By default, use first available node. :param wait: If `True`, wait for the cluster to become active before creating the proxy :returns: :py:class:`~subprocess.Popen` object representing the SSH connection. """ if port is None: port = 12345 cluster, nodes = self._ssh_cluster_nodes(cluster_id, wait=wait) ssh_node = self._get_named_node(nodes, node_name=node_name) # Get a URL to test the proxy against all_urls = itertools.chain.from_iterable([ component['uri'] for component in node.components if 'uri' in component and component['uri'].startswith('http') ] for node in nodes) test_url = six.next(all_urls, None) printer = self._cli_printer(LOG) printer('Starting SOCKS proxy via node {0} ({1})'.format( ssh_node.name, ssh_node.public_ip)) process = create_socks_proxy(cluster.username, ssh_node.public_ip, port, ssh_command=ssh_command, test_url=test_url) printer( 'Successfully created SOCKS proxy on localhost:{0}'.format(port), logging.INFO) if not self._command_line: return process try: printer('Use Ctrl-C to stop proxy', logging.NOTSET) process.communicate() except KeyboardInterrupt: printer('SOCKS proxy closed') @command(parser_options=dict( description='SSH to a node in the cluster and optionally execute ' 'a command'), node_name=argument(help='Name of node on which to make the SSH ' 'connection, e.g. gateway-1. By default, use ' 'first available node.'), ssh_command=argument(help="SSH command (default: 'ssh')"), wait=argument( action='store_true', help="Wait for cluster to become active (if it isn't " "already)"), command=argument(help='Command to execute over SSH')) def _ssh(self, cluster_id, node_name=None, ssh_command=None, wait=False, command=None): """ Command-line only. SSH to the desired cluster node. """ output = self._execute_ssh(cluster_id, node_name=node_name, ssh_command=ssh_command, wait=wait, command=command) if command: six.print_(output) def _execute_ssh(self, cluster_id, node_name=None, ssh_command=None, wait=False, command=None): cluster, nodes = self._ssh_cluster_nodes(cluster_id, wait=wait) node = self._get_named_node(nodes, node_name=node_name) return node._ssh(cluster.username, command=command, ssh_command=ssh_command) def ssh_execute(self, cluster_id, node_name, command, ssh_command=None, wait=False): """ Execute a command over SSH to the specified node in the cluster. :param cluster_id: Cluster ID :param node_name: Name of node on which to make the SSH connection. By default, use first available node. :param command: Shell command to execute remotely :param ssh_command: SSH shell command to execute locally (default: `'ssh'`) :param wait: If `True`, wait for the cluster to become active before creating the proxy :returns: The output of running the command """ return self._execute_ssh(cluster_id, node_name=node_name, ssh_command=ssh_command, command=command, wait=wait) @command( parser_options=dict(description='Create SSH tunnel'), local_port=argument(type=int, help='Port to which to bind on localhost'), remote_port=argument(type=int, help='Port to which to bind on cluster node'), node_name=argument(help='Name of node on which to make the SSH ' 'connection, e.g. gateway-1. Mutually ' 'exclusive with --component'), component=argument(help='Find and connect to node containing a ' 'particular component, e.g. HiveServer2. ' 'Mutually exclusive with --node-name'), ssh_command=argument(help="SSH command (default: 'ssh')"), wait=argument(action='store_true', help="Wait for cluster to become active (if it isn't " "already)"), ) def ssh_tunnel(self, cluster_id, local_port, remote_port, node_name=None, component=None, ssh_command=None, wait=False): """ Create a SSH tunnel from the local host to a particular port on a cluster node. Returns the SSH process (via :py:class:`~subprocess.Popen`), which can be stopped via the :func:`kill` method. :param cluster_id: Cluster ID :param local_port: Port on which to bind on localhost :param remote_port: Port on which to bind on the cluster node :param node_name: Name of node on which to make the SSH connection. Mutually exclusive with `component` :param component: Name of a component installed on a node in the cluster, e.g. `HiveServer2`. SSH tunnel will be set up on the first node containing the component. Mutually exclusive with `node_name` :param wait: If `True`, wait for the cluster to become active before creating the proxy :returns: :py:class:`~subprocess.Popen` object representing the SSH connection. """ if node_name and component: raise error.InvalidError( 'node_name and component are mutually exclusive') elif not (node_name or component): raise error.InvalidError( 'One of node_name or component is required') cluster, nodes = self._ssh_cluster_nodes(cluster_id, wait=wait) if component: ssh_node = self._get_component_node(nodes, component) else: ssh_node = self._get_named_node(nodes, node_name=node_name) printer = self._cli_printer(LOG) printer('Starting SSH tunnel from localhost:{0} to {1}:{2} ' '({3})'.format(local_port, ssh_node.name, remote_port, ssh_node.public_ip)) process = create_ssh_tunnel(cluster.username, ssh_node, local_port, remote_port, ssh_command=ssh_command) printer( 'Successfully created SSH tunnel to localhost:{0}'.format( local_port), logging.INFO) if not self._command_line: return process try: printer('Use Ctrl-C to stop tunnel', logging.NOTSET) process.communicate() except KeyboardInterrupt: printer('SSH tunnel closed') def services(self, action, cluster_id, service_names): data = { 'cluster': { 'control_services': { 'action': action, 'services': [{ 'name': name } for name in service_names], } } } return self._parse_response(self._client._put( 'clusters/{0}'.format(cluster_id), json=data), ClusterResponse, wrapper='cluster') @command( parser_options=dict( description='Start/stop/restart services on a cluster'), action=argument(choices=['start', 'stop', 'restart']), service_names=argument(metavar='<service>', nargs='*', help='Cluster service name, e.g. HDFS'), force=argument(action='store_true', help="Don't display confirmation dialog"), ) @display_table(ClusterDetail) def _services(self, action, cluster_id, service_names, force=False): if not force: if service_names: services_str = 'service{0} {1}'.format( 's' if len(service_names) > 1 else '', ', '.join(service_names)) else: services_str = 'all services' dialog = '{0} {1} on cluster {2}?'.format(action.capitalize(), services_str, cluster_id) if not confirm(dialog): return return self.services(action, cluster_id, service_names)
class Resource(resource.Resource): """Flavors API methods""" @command(parser_options=dict(description='List all existing stacks', )) @display(display_stacks) def list(self): """ List all stacks :returns: List of :class:`~lavaclient.api.response.Stack` objects """ return self._parse_response(self._client._get('stacks'), StacksResponse, wrapper='stacks') @command(parser_options=dict( description='Show a specific stack in detail', )) @display_table(StackDetail) def get(self, stack_id): """ Get a specific stack :param stack_id: Stack ID :returns: :class:`~lavaclient.api.response.StackDetail` """ return self._parse_response(self._client._get( 'stacks/{0}'.format(stack_id)), StackResponse, wrapper='stack') @command(parser_options=dict( description='Create a custom stack', epilog=SERVICE_CREATE_EPILOG, ), name=argument(help='A stack identifier, e.g. MY_HADOOP_STACK'), distro=argument(help='An existing distribution ID; see ' '`lava distros list`'), services=argument( type=read_json, help='JSON data string or path to file containing ' 'JSON data; see SERVICES'), node_groups=argument(type=read_json, help='JSON data string or p ath to file ' 'containing JSON data; see NODE GROUPS'), description=argument(help='A brief description of the purpose ' 'of the stack')) @display_table(StackDetail) def create(self, name, distro, services, node_groups=None, description=None): """ Create a stack :param name: Stack name :param distro: Valid distro identifier :param services: List of services. Each should have a name and optionally a list of modes :param node_groups: List of node groups for the cluster :returns: :class:`~lavaclient.api.response.StackDetail` """ data = dict( name=name, distro=distro, services=services, ) if node_groups: data.update(node_groups=node_groups) if description: data.update(description=description) request_data = self._marshal_request(data, CreateStackRequest, wrapper='stack') return self._parse_response(self._client._post('stacks', json=request_data), StackResponse, wrapper='stack') @command( parser_options=dict(description='Delete a custom stack'), force=argument(action='store_true', help='Do not show confirmation dialog'), ) def _delete(self, stack_id, force=False): if not force: display_result(self.get(stack_id), StackDetail) if not confirm('Delete this stack?'): return self.delete(stack_id) def delete(self, stack_id): """ Delete a stack :param stack_id: Stack ID """ self._client._delete('stacks/{0}'.format(stack_id))
class Resource(resource.Resource): """Scripts API methods""" @command(parser_options=dict( description='List all existing cluster scripts', )) @display_table(Script) def list(self): """ List scripts that belong to the tenant specified in the client :returns: List of :class:`~lavaclient.api.response.Script` objects """ return self._parse_response( self._client._get('scripts'), ScriptsResponse, wrapper='scripts') @command( parser_options=dict( description='Create a cluster script', ), name=argument(help='Descriptive name for this script'), url=argument(help='The URL from which the script may be downloaded'), script_type=argument(metavar='<type>', choices=['post_init'], help='The type of script, e.g post-init script') ) @display_table(Script) def create(self, name, url, script_type): """ Create a script. Currently only post-init scripts are supported. :param name: Script name :param url: The URL from which the script may be downloaded :param script_type: Script type; currently, must be 'post_init' :returns: :class:`~lavaclient.api.response.Script` """ data = dict( name=name, url=url, type=script_type.upper(), ) request_data = self._marshal_request( data, CreateScriptRequest, wrapper='script') return self._parse_response( self._client._post('scripts', json=request_data), ScriptResponse, wrapper='script') @command( parser_options=dict( description='Update an existing script', ), name=argument(help='Descriptive name for this script'), url=argument(help='The URL from which the script may be downloaded'), script_type=argument('--type', choices=['post_init'], help='The type of script, e.g post-init script') ) @display_table(Script) def update(self, script_id, name=None, url=None, script_type=None): """ Update an existing script. :param script_id: ID of existing script :param name: Script name :param url: The URL from which the script may be downloaded :param script_type: Script type; currently, must be 'post_init' :returns: :class:`~lavaclient.api.response.Script` """ params = [('name', name), ('url', url), ('type', script_type.upper() if script_type else None)] data = {} for key, value in params: if value is not None: data[key] = value request_data = self._marshal_request( data, UpdateScriptRequest, wrapper='script') return self._parse_response( self._client._put('scripts/{0}'.format(script_id), json=request_data), ScriptResponse, wrapper='script') @command( parser_options=dict(description='Delete a cluster script'), do_confirm=argument('--force', action='store_false', help='Suppress delete confirmation dialog'), ) def _delete(self, script_id, do_confirm=True): if do_confirm and not confirm('Delete script {0}?'.format(script_id)): return return self.delete(script_id) def delete(self, script_id): """ Delete a script. :param script_id: ID of existing script """ self._client._delete('scripts/{0}'.format(script_id))
class Resource(resource.Resource): """Credentials API methods""" def _list(self, type=None): url = 'credentials/' + type if type else 'credentials' resp = self._parse_response(self._client._get(url), CredentialsResponse, wrapper='credentials') return getattr(resp, type) if type else resp @command(parser_options=dict(description='List all existing credentials', ) ) @display_table(Credentials) def list(self): """ List all credentials belonging to the tenant :returns: List of :class:`Credentials` objects """ return self._list() @command(parser_options=dict(description='List all SSH keys')) @display_table(SSHKey) def list_ssh_keys(self): """ List all SSH keys :returns: List of :class:`SSHKey` objects """ return self._list(type='ssh_keys') @command( parser_options=dict(description='List all Cloud Files credentials')) @display_table(CloudFilesCredential) def list_cloud_files(self): """ List all Cloud Files credentials :returns: List of :class:`CloudFilesCredential` objects """ return self._list(type='cloud_files') @command(parser_options=dict(description='List all Amazon S3 credentials')) @display_table(S3Credential) def list_s3(self): """ List all Amazon S3 credentials :returns: List of :class:`S3Credential` objects """ return self._list(type='s3') @command(parser_options=dict(description='List all Ambari credentials')) @display_table(AmbariCredential) def list_ambari(self): """ List all Ambari credentials :returns: List of :class:`AmbariCredential` objects """ return self._list(type='ambari') def list_types(self): """ List all credential types :returns: List of CredentialType objects """ return self._parse_response(self._get('credentials/types'), CredentialTypesResponse, wrapper='credentials') @command(parser_options=dict( description='Upload a SSH public key for cluster logins'), name=argument(metavar='<name>', help='Name to associate with the key'), public_key=argument( help='SSH public key; must be either a file containing the ' 'public key or the plaintext public key itself.')) @display_table(SSHKey) def create_ssh_key(self, name, public_key): """ Upload an SSH public key for cluster logins :param name: The name to associate to the public key :param public_key: SSH public key in plaintext or path to key file :returns: :class:`SSHKey` """ data = dict( key_name=name, public_key=file_or_string(public_key), ) request_data = self._marshal_request(data, CreateSSHKeyRequest, wrapper='ssh_keys') resp = self._parse_response(self._client._post('credentials/ssh_keys', json=request_data), CredentialResponse, wrapper='credentials') return resp.ssh_keys @command(parser_options=dict( description='Add credentials for a Cloud Files account'), username=argument(help='Cloud Files username'), api_key=argument(help='Cloud Files API key')) @display_table(CloudFilesCredential) def create_cloud_files(self, username, api_key): """ Create credentials for Cloud Files access :param username: Cloud Files username :param api_key: Cloud Files API Key :returns: :class:`CloudFilesCredential` """ data = dict( username=username, api_key=api_key, ) request_data = self._marshal_request(data, CreateCloudFilesRequest, wrapper='cloud_files') resp = self._parse_response(self._client._post( 'credentials/cloud_files', json=request_data), CredentialResponse, wrapper='credentials') return resp.cloud_files @command(parser_options=dict(description='Add credentials for Amazon S3'), access_key_id=argument(help='Amazon S3 access key id'), access_secret_key=argument(help='Amazon S3 access secret key')) @display_table(S3Credential) def create_s3(self, access_key_id, access_secret_key): """ Create credentials for Amazon S3 access :param access_key_id: S3 access key id :param access_secret_key: S3 access secret key :returns: :class:`S3Credential` """ data = dict( access_key_id=access_key_id, access_secret_key=access_secret_key, ) request_data = self._marshal_request(data, CreateS3Request, wrapper='s3') resp = self._parse_response(self._client._post('credentials/s3', json=request_data), CredentialResponse, wrapper='credentials') return resp.s3 @command(parser_options=dict(description='Add credentials for Ambari'), username=argument(help='Ambari username'), ambari_password=argument(help='Password')) @display_table(AmbariCredential) def _create_ambari(self, username, ambari_password=None): if ambari_password is None: ambari_password = getpass('Password for {0}: '.format(username)) return self.create_ambari(username, ambari_password) def create_ambari(self, username, password): """ Create credentials for Ambari access :param username: Ambari username :param password: Password :returns: :class:`AmbariCredential` """ data = dict( username=username, password=password, ) request_data = self._marshal_request(data, CreateAmbariRequest, wrapper='ambari') resp = self._parse_response(self._client._post('credentials/ambari', json=request_data), CredentialResponse, wrapper='credentials') return resp.ambari @command(parser_options=dict(description='Update SSH key'), name=argument(metavar='<name>', help='Name of existing SSH key'), public_key=argument( help='SSH public key; must be either a file containing the ' 'public key or the plaintext public key itself.')) @display_table(SSHKey) def update_ssh_key(self, name, public_key): """ Upload an SSH public key for cluster logins :param name: The name of an existing SSH key :param public_key: SSH public key in plaintext or path to key file :returns: :class:`SSHKey` """ data = dict(key_name=name, public_key=file_or_string(public_key)) request_data = self._marshal_request(data, CreateSSHKeyRequest, wrapper='ssh_keys') resp = self._parse_response(self._client._put( 'credentials/ssh_keys/{0}'.format(name), json=request_data), CredentialResponse, wrapper='credentials') return resp.ssh_keys @command( parser_options=dict( description='Add credentials for a Cloud Files account'), username=argument(help='Username for existing Cloud Files credential'), api_key=argument(help='Cloud Files API key')) @display_table(CloudFilesCredential) def update_cloud_files(self, username, api_key): """ Update credentials for Cloud Files access :param username: Cloud Files username :param api_key: Cloud Files API Key :returns: :class:`CloudFilesCredential` """ data = dict(username=username, api_key=api_key) request_data = self._marshal_request(data, CreateCloudFilesRequest, wrapper='cloud_files') resp = self._parse_response(self._client._put( 'credentials/cloud_files/{0}'.format(username), json=request_data), CredentialResponse, wrapper='credentials') return resp.cloud_files @command( parser_options=dict(description='Update credentials for Amazon S3'), access_key_id=argument( help='Access Key Id for existing Amazon S3 credential'), access_secret_key=argument(help='Amazon S3 Access Secret Key')) @display_table(S3Credential) def update_s3(self, access_key_id, access_secret_key): """ Update credentials for Amazon S3 access :param access_key_id: S3 access key id :param access_secret_key: S3 access secret Key :returns: :class:`S3Credential` """ data = dict(access_key_id=access_key_id, access_secret_key=access_secret_key) request_data = self._marshal_request(data, CreateS3Request, wrapper='s3') resp = self._parse_response(self._client._put( 'credentials/s3/{0}'.format(access_key_id), json=request_data), CredentialResponse, wrapper='credentials') return resp.s3 @command(parser_options=dict(description='Update credentials for Ambari'), username=argument(help='Username for existing Ambari credential'), ambari_password=argument(help='Password', required=False)) @display_table(AmbariCredential) def _update_ambari(self, username, ambari_password=None): if ambari_password is None: ambari_password = getpass('Password for {0}: '.format(username)) return self.update_ambari(username, ambari_password) def update_ambari(self, username, password): """ Update credentials for Ambari access :param username: Ambari username :param password: Password :returns: :class:`AmbariCredential` """ data = dict(username=username, password=password) request_data = self._marshal_request(data, CreateAmbariRequest, wrapper='ambari') resp = self._parse_response(self._client._put( 'credentials/ambari/{0}'.format(username), json=request_data), CredentialResponse, wrapper='credentials') return resp.ambari @command( parser_options=dict(description='Delete an SSH key'), name=argument(help='SSH key name'), do_confirm=argument('--force', action='store_false', help='Suppress delete confirmation dialog'), ) def _delete_ssh_key(self, name, do_confirm=True): if do_confirm and not confirm('Delete SSH key {0}?'.format(name)): return return self.delete_ssh_key(name) def delete_ssh_key(self, name): """ Delete SSH key :param name: Name that identifies the SSH key """ self._client._delete('credentials/ssh_keys/{0}'.format(name)) @command( parser_options=dict(description='Delete a Cloud Files credential'), username=argument(help='Cloud Files username'), do_confirm=argument('--force', action='store_false', help='Suppress delete confirmation dialog'), ) def _delete_cloud_files(self, username, do_confirm=True): if do_confirm and not confirm( 'Delete Cloud Files username {0}?'.format(username)): return return self.delete_cloud_files(username) def delete_cloud_files(self, username): """ Delete Cloud Files credential :param username: Cloud Files username """ self._client._delete('credentials/cloud_files/{0}'.format(username)) @command( parser_options=dict(description='Delete Amazon S3 credential'), access_key_id=argument(help='Amazon S3 access key id'), do_confirm=argument('--force', action='store_false', help='Suppress delete confirmation dialog'), ) def _delete_s3(self, access_key_id, do_confirm=True): if do_confirm and not confirm( 'Delete S3 access key {0}?'.format(access_key_id)): return return self.delete_s3(access_key_id) def delete_s3(self, access_key_id): """ Delete Amazon s3 credential :param access_key_id: S3 access key id """ self._client._delete('credentials/s3/{0}'.format(access_key_id)) @command( parser_options=dict(description='Delete Ambari credential'), username=argument(help='Ambari username'), do_confirm=argument('--force', action='store_false', help='Suppress delete confirmation dialog'), ) def _delete_ambari(self, username, do_confirm=True): if do_confirm and not confirm( 'Delete Ambari user {0}?'.format(username)): return return self.delete_ambari(username) def delete_ambari(self, username): """ Delete Ambari credential :param username: Ambari username """ self._client._delete('credentials/ambari/{0}'.format(username))