awscli-1.2.9/0000755€BÈÀ´00000000000000000012254752016014570 5ustar jamessarwheel00000000000000awscli-1.2.9/awscli/0000755€BÈÀ´00000000000000000012254752016016052 5ustar jamessarwheel00000000000000awscli-1.2.9/awscli/__init__.py0000644€BÈÀ´00000000000000277612254751606020203 0ustar jamessarwheel00000000000000# Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://aws.amazon.com/apache2.0/ # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. """ AWSCLI ---- A Universal Command Line Environment for Amazon Web Services. """ import os __version__ = '1.2.9' # # Get our data path to be added to botocore's search path # _awscli_data_path = [ os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data') ] if 'AWS_DATA_PATH' in os.environ: for path in os.environ['AWS_DATA_PATH'].split(os.pathsep): path = os.path.expandvars(path) path = os.path.expanduser(path) _awscli_data_path.append(path) os.environ['AWS_DATA_PATH'] = os.pathsep.join(_awscli_data_path) EnvironmentVariables = { 'profile': (None, 'AWS_DEFAULT_PROFILE', None), 'region': ('region', 'AWS_DEFAULT_REGION', None), 'data_path': ('data_path', 'AWS_DATA_PATH', None), 'output': ('output', 'AWS_DEFAULT_OUTPUT', 'json'), } SCALAR_TYPES = set([ 'string', 'float', 'integer', 'long', 'boolean', 'double', 'blob', 'timestamp' ]) COMPLEX_TYPES = set(['structure', 'map', 'list']) awscli-1.2.9/awscli/argparser.py0000644€BÈÀ´00000000000001163212254746560020424 0ustar jamessarwheel00000000000000# Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://aws.amazon.com/apache2.0/ # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. import argparse from difflib import get_close_matches class CLIArgParser(argparse.ArgumentParser): Formatter = argparse.RawTextHelpFormatter # When displaying invalid choice error messages, # this controls how many options to show per line. ChoicesPerLine = 2 def _check_value(self, action, value): """ It's probably not a great idea to override a "hidden" method but the default behavior is pretty ugly and there doesn't seem to be any other way to change it. """ # converted value must be one of the choices (if specified) if action.choices is not None and value not in action.choices: msg = ['Invalid choice, valid choices are:\n'] for i in range(len(action.choices))[::self.ChoicesPerLine]: current = [] for choice in action.choices[i:i+self.ChoicesPerLine]: current.append('%-40s' % choice) msg.append(' | '.join(current)) possible = get_close_matches(value, action.choices, cutoff=0.8) if possible: extra = ['\n\nInvalid choice: %r, maybe you meant:\n' % value] for word in possible: extra.append(' * %s' % word) msg.extend(extra) raise argparse.ArgumentError(action, '\n'.join(msg)) class MainArgParser(CLIArgParser): Formatter = argparse.RawTextHelpFormatter def __init__(self, command_table, version_string, description, usage, argument_table): super(MainArgParser, self).__init__( formatter_class=self.Formatter, add_help=False, conflict_handler='resolve', description=description, usage=usage) self._build(command_table, version_string, argument_table) def _create_choice_help(self, choices): help_str = '' for choice in sorted(choices): help_str += '* %s\n' % choice return help_str def _build(self, command_table, version_string, argument_table): for argument_name in argument_table: argument = argument_table[argument_name] argument.add_to_parser(self) self.add_argument('--version', action="version", version=version_string, help='Display the version of this tool') self.add_argument('command', choices=list(command_table.keys())) class ServiceArgParser(CLIArgParser): Usage = ("aws [options] [parameters]") def __init__(self, operations_table, service_name): super(ServiceArgParser, self).__init__( formatter_class=argparse.RawTextHelpFormatter, add_help=False, conflict_handler='resolve', usage=self.Usage) self._build(operations_table) self._service_name = service_name def _build(self, operations_table): self.add_argument('operation', choices=list(operations_table.keys())) class ArgTableArgParser(CLIArgParser): """CLI arg parser based on an argument table.""" Usage = ("aws [options] [parameters]") def __init__(self, argument_table, command_table=None): # command_table is an optional subcommand_table. If it's passed # in, then we'll update the argparse to parse a 'subcommand' argument # and populate the choices field with the command table keys. super(ArgTableArgParser, self).__init__( formatter_class=self.Formatter, add_help=False, usage=self.Usage, conflict_handler='resolve') if command_table is None: command_table = {} self._build(argument_table, command_table) def _build(self, argument_table, command_table): for arg_name in argument_table: argument = argument_table[arg_name] argument.add_to_parser(self) if command_table: self.add_argument('subcommand', choices=list(command_table.keys()), nargs='?') def parse_known_args(self, args, namespace=None): if len(args) == 1 and args[0] == 'help': namespace = argparse.Namespace() namespace.help = 'help' return namespace, [] else: return super(ArgTableArgParser, self).parse_known_args( args, namespace) awscli-1.2.9/awscli/argprocess.py0000644€BÈÀ´00000000000003675612254746560020624 0ustar jamessarwheel00000000000000# Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://aws.amazon.com/apache2.0/ # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. """Module for processing CLI args.""" import os import logging import six from botocore.compat import OrderedDict, json from awscli import utils from awscli import SCALAR_TYPES, COMPLEX_TYPES LOG = logging.getLogger('awscli.argprocess') class ParamError(Exception): def __init__(self, param, message): full_message = ("Error parsing parameter %s, should be: %s" % (param.cli_name, message)) super(ParamError, self).__init__(full_message) self.param = param class ParamSyntaxError(Exception): pass class ParamUnknownKeyError(Exception): def __init__(self, param, key, valid_keys): valid_keys = ', '.join(valid_keys) full_message = ( "Unknown key '%s' for parameter %s, valid choices " "are: %s" % (key, param.cli_name, valid_keys)) super(ParamUnknownKeyError, self).__init__(full_message) def detect_shape_structure(param): if param.type in SCALAR_TYPES: return 'scalar' elif param.type == 'structure': sub_types = [detect_shape_structure(p) for p in param.members] # We're distinguishing between structure(scalar) # and structure(scalars), because for the case of # a single scalar in a structure we can simplify # more than a structure(scalars). if len(sub_types) == 1 and all(p == 'scalar' for p in sub_types): return 'structure(scalar)' elif len(sub_types) > 1 and all(p == 'scalar' for p in sub_types): return 'structure(scalars)' else: return 'structure(%s)' % ', '.join(sorted(set(sub_types))) elif param.type == 'list': return 'list-%s' % detect_shape_structure(param.members) elif param.type == 'map': if param.members.type in SCALAR_TYPES: return 'map-scalar' else: return 'map-%s' % detect_shape_structure(param.members) class ParamShorthand(object): # To add support for a new shape: # # * Add it to SHORTHAND_SHAPES below, key is the shape structure # value is the name of the method to call. # * Implement parse method. # * Implement _doc_. This is used to generate # the docs for this shorthand syntax. SHORTHAND_SHAPES = { 'structure(scalars)': '_key_value_parse', 'structure(scalar)': '_special_key_value_parse', 'map-scalar': '_key_value_parse', 'list-structure(scalar)': '_list_scalar_parse', 'list-structure(scalars)': '_list_key_value_parse', 'list-structure(list-scalar, scalar)': '_list_scalar_list_parse', } def __init__(self): pass def __call__(self, param, value, **kwargs): """Attempt to parse shorthand syntax for values. This is intended to be hooked up as an event handler (hence the **kwargs). Given ``param`` object and its string ``value``, figure out if we can parse it. If we can parse it, we return the parsed value (typically some sort of python dict). :type param: :class:`botocore.parameters.Parameter` :param param: The parameter object (includes various metadata about the parameter). :type value: str :param value: The value for the parameter type on the command line, e.g ``--foo this_value``, value would be ``"this_value"``. :returns: If we can parse the value we return the parsed value. If it looks like JSON, we return None (which tells the event emitter to use the default ``unpack_cli_arg`` provided that no other event handlers can parsed the value). If we run into an error parsing the value, a ``ParamError`` will be raised. """ parse_method = self.get_parse_method_for_param(param, value) if parse_method is None: return else: try: LOG.debug("Using %s for param %s", parse_method, param) parsed = getattr(self, parse_method)(param, value) except ParamSyntaxError as e: doc_fn = self._get_example_fn(param) # Try to give them a helpful error message. if doc_fn is None: raise e else: raise ParamError(param, doc_fn(param)) return parsed def get_parse_method_for_param(self, param, value=None): # We first need to make sure this is a parameter that qualifies # for simplification. The first short-circuit case is if it looks # like json we immediately return. if isinstance(value, list): check_val = value[0] else: check_val = value if isinstance(check_val, str) and check_val.startswith(('[', '{')): LOG.debug("Param %s looks like JSON, not considered for " "param shorthand.", param.py_name) return structure = detect_shape_structure(param) parse_method = self.SHORTHAND_SHAPES.get(structure) return parse_method def _get_example_fn(self, param): doc_fn = None shape_structure = detect_shape_structure(param) method = self.SHORTHAND_SHAPES.get(shape_structure) if method: doc_fn = getattr(self, '_docs' + method, None) return doc_fn def add_example_fn(self, arg_name, help_command, **kwargs): """ Adds a callable to the ``example_fn`` attribute of the parameter if the parameter type is supported by shorthand syntax. This callable should return a string containing just the example and not any of the ReST formatting that might be required in the docs. """ argument = help_command.arg_table[arg_name] if hasattr(argument, 'argument_object') and argument.argument_object: param = argument.argument_object LOG.debug('Adding example fn for: %s' % param.name) doc_fn = self._get_example_fn(param) param.example_fn = doc_fn def _list_scalar_list_parse(self, param, value): # Think something like ec2.DescribeInstances.Filters. # We're looking for key=val1,val2,val3,key2=val1,val2. arg_types = {} for arg in param.members.members: arg_types[arg.name] = arg.type parsed = [] for v in value: parts = self._split_on_commas(v) current_parsed = {} current_key = None for part in parts: current = part.split('=', 1) if len(current) == 2: # This is a key/value pair. current_key = current[0].strip() current_value = current[1].strip() if current_key not in arg_types: raise ParamUnknownKeyError(param, current_key, arg_types.keys()) elif arg_types[current_key] == 'list': current_parsed[current_key] = [current_value] else: current_parsed[current_key] = current_value elif current_key is not None: # This is a value which we associate with the current_key, # so key1=val1,val2 # ^ # | # val2 is associated with key1. current_parsed[current_key].append(current[0]) else: raise ParamSyntaxError(part) parsed.append(current_parsed) return parsed def _list_scalar_parse(self, param, value): single_param = param.members.members[0] parsed = [] # We know that value is a list in this case. for v in value: parsed.append({single_param.name: v}) return parsed def _list_key_value_parse(self, param, value): # param is a list param. # param.member is the struct param. struct_param = param.members parsed = [] for v in value: single_struct_param = self._key_value_parse(struct_param, v) parsed.append(single_struct_param) return parsed def _special_key_value_parse(self, param, value): # This is a special key value parse that can do the normal # key=value parsing, *but* supports a few additional conveniences # when working with a structure with a single element. # Precondition: param is a shape of structure(scalar) if len(param.members) == 1 and param.members[0].name == 'Value' and \ '=' not in value: # We have an even shorter shorthand syntax for structure # of scalars of a single element with a member name of # 'Value'. return {'Value': value} else: return self._key_value_parse(param, value) def _key_value_parse(self, param, value): # The expected structure is: # key=value,key2=value # that is, csv key value pairs, where the key and values # are separated by '='. All of this should be whitespace # insensitive. parsed = OrderedDict() parts = self._split_on_commas(value) valid_names = self._create_name_to_params(param) for part in parts: try: key, value = part.split('=', 1) except ValueError: raise ParamSyntaxError(part) key = key.strip() value = value.strip() if valid_names and key not in valid_names: raise ParamUnknownKeyError(param, key, valid_names) if valid_names: sub_param = valid_names[key] if sub_param is not None: value = unpack_scalar_cli_arg(sub_param, value) parsed[key] = value return parsed def _create_name_to_params(self, param): if param.type == 'structure': return dict([(p.name, p) for p in param.members]) elif param.type == 'map' and hasattr(param.keys, 'enum'): return dict([(v, None) for v in param.keys.enum]) def _docs_list_scalar_list_parse(self, param): s = 'Key value pairs, where values are separated by commas.\n' s += '%s ' % param.cli_name inner_params = param.members.members scalar_params = [p for p in inner_params if p.type in SCALAR_TYPES] list_params = [p for p in inner_params if p.type == 'list'] for param in scalar_params: s += '%s=%s1,' % (param.name, param.type) for param in list_params[:-1]: param_type = param.members.type s += '%s=%s1,%s2,' % (param.name, param_type, param_type) last_param = list_params[-1] param_type = last_param.members.type s += '%s=%s1,%s2' % (last_param.name, param_type, param_type) return s def _docs_list_scalar_parse(self, param): name = param.members.members[0].name return '%s %s1 %s2 %s3' % (param.cli_name, name, name, name) def _docs_list_key_value_parse(self, param): s = "Key value pairs, with multiple values separated by a space.\n" s += '%s ' % param.cli_name s += ','.join(['%s=%s' % (sub_param.name, sub_param.type) for sub_param in param.members.members]) return s def _docs_special_key_value_parse(self, param): if len(param.members) == 1 and param.members[0].name == 'Value': # Returning None will indicate that we don't have # any examples to generate, and the entire examples section # should be skipped for this arg. return None else: self._docs_key_value_parse(param) def _docs_key_value_parse(self, param): s = '%s ' % param.cli_name if param.type == 'structure': s += ','.join(['%s=value' % sub_param.name for sub_param in param.members]) elif param.type == 'map': s += 'key_name=string,key_name2=string' if param.keys.type == 'string' and hasattr(param.keys, 'enum'): s += '\nWhere valid key names are:\n' for value in param.keys.enum: s += ' %s\n' % value return s def _split_on_commas(self, value): try: return utils.split_on_commas(value) except ValueError as e: raise ParamSyntaxError(str(e)) def unpack_cli_arg(parameter, value): """ Parses and unpacks the encoded string command line parameter and returns native Python data structures that can be passed to the Operation. :type parameter: :class:`botocore.parameter.Parameter` :param parameter: The parameter object containing metadata about the parameter. :param value: The value of the parameter. This can be a number of different python types (str, list, etc). This is the value as it's specified on the command line. :return: The "unpacked" argument than can be sent to the `Operation` object in python. """ if parameter.type in SCALAR_TYPES: return unpack_scalar_cli_arg(parameter, value) elif parameter.type in COMPLEX_TYPES: return unpack_complex_cli_arg(parameter, value) else: return str(value) def unpack_complex_cli_arg(parameter, value): if parameter.type == 'structure' or parameter.type == 'map': if value.lstrip()[0] == '{': d = json.loads(value, object_pairs_hook=OrderedDict) else: msg = 'The value for parameter "%s" must be JSON or path to file.' % ( parameter.cli_name) raise ValueError(msg) return d elif parameter.type == 'list': if isinstance(value, six.string_types): if value.lstrip()[0] == '[': return json.loads(value, object_pairs_hook=OrderedDict) elif isinstance(value, list) and len(value) == 1: single_value = value[0].strip() if single_value and single_value[0] == '[': return json.loads(value[0], object_pairs_hook=OrderedDict) return [unpack_cli_arg(parameter.members, v) for v in value] def unpack_scalar_cli_arg(parameter, value): if parameter.type == 'integer' or parameter.type == 'long': return int(value) elif parameter.type == 'float' or parameter.type == 'double': # TODO: losing precision on double types return float(value) elif parameter.type == 'blob' and parameter.payload and parameter.streaming: file_path = os.path.expandvars(value) file_path = os.path.expanduser(file_path) if not os.path.isfile(file_path): msg = 'Blob values must be a path to a file.' raise ValueError(msg) return open(file_path, 'rb') elif parameter.type == 'boolean': if isinstance(value, str) and value.lower() == 'false': return False return bool(value) else: return str(value) awscli-1.2.9/awscli/arguments.py0000644€BÈÀ´00000000000003467712254746560020461 0ustar jamessarwheel00000000000000# Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://aws.amazon.com/apache2.0/ # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. """Abstractions for CLI arguments. This module contains abstractions for representing CLI arguments. This includes how the CLI argument parser is created, how arguments are serialized, and how arguments are bound (if at all) to operation arguments. """ import logging from botocore.hooks import first_non_none_response from botocore import xform_name from awscli.paramfile import get_paramfile, ResourceLoadingError from awscli.argprocess import unpack_cli_arg LOG = logging.getLogger('awscli.arguments') class BadArgumentError(Exception): pass class UnknownArgumentError(Exception): pass class BaseCLIArgument(object): """Interface for CLI argument. This class represents the interface used for representing CLI arguments. """ def __init__(self, name): self._name = name def add_to_arg_table(self, argument_table): """Add this object to the argument_table. The ``argument_table`` represents the argument for the operation. This is called by the ``ServiceOperation`` object to create the arguments associated with the operation. :type argument_table: dict :param argument_table: The argument table. The key is the argument name, and the value is an object implementing this interface. """ argument_table[self.name] = self def add_to_parser(self, parser): """Add this object to the parser instance. This method is called by the associated ``ArgumentParser`` instance. This method should make the relevant calls to ``add_argument`` to add itself to the argparser. :type parser: ``argparse.ArgumentParser``. :param parser: The argument parser associated with the operation. """ pass def add_to_params(self, parameters, value): """Add this object to the parameters dict. This method is responsible for taking the value specified on the command line, and deciding how that corresponds to parameters used by the service/operation. :type parameters: dict :param parameters: The parameters dictionary that will be given to ``botocore``. This should match up to the parameters associated with the particular operation. :param value: The value associated with the CLI option. """ pass @property def name(self): return self._name @property def cli_name(self): return '--' + self._name @property def cli_type_name(self): raise NotImplementedError("cli_type_name") @property def required(self): raise NotImplementedError("required") @property def documentation(self): raise NotImplementedError("documentation") @property def cli_type(self): raise NotImplementedError("cli_type") @property def py_name(self): return self._name.replace('-', '_') @property def choices(self): """List valid choices for argument value. If this value is not None then this should return a list of valid values for the argument. """ return None @name.setter def name(self, value): self._name = value @property def group_name(self): """Get the group name associated with the argument. An argument can be part of a group. This property will return the name of that group. This base class has no default behavior for groups, code that consumes argument objects can use them for whatever purposes they like (documentation, mutually exclusive group validation, etc.). """ return None class CustomArgument(BaseCLIArgument): """ Represents a CLI argument that is configured from a dictionary. For example, the "top level" arguments used for the CLI (--region, --output) can use a DictBasedArgument argument, as these are described in the cli.json file as dictionaries. """ def __init__(self, name, help_text='', dest=None, default=None, action=None, required=None, choices=None, nargs=None, cli_type_name=None, group_name=None): self._name = name self._help = help_text self._dest = dest self._default = default self._action = action self._required = required self._nargs = nargs self._cli_type_name = cli_type_name self._group_name = group_name if choices is None: choices = [] self._choices = choices # TODO: We should eliminate this altogether. # You should not have to depend on an argument_object # as part of the interface. Currently the argprocess # and docs code relies on this object. self.argument_object = None def add_to_parser(self, parser): """ See the ``BaseCLIArgument.add_to_parser`` docs for more information. """ cli_name = self.cli_name kwargs = {} if self._dest is not None: kwargs['dest'] = self._dest if self._action is not None: kwargs['action'] = self._action if self._default is not None: kwargs['default'] = self._default if self._choices: kwargs['choices'] = self._choices if self._required is not None: kwargs['required'] = self._required if self._nargs is not None: kwargs['nargs'] = self._nargs parser.add_argument(cli_name, **kwargs) def required(self): if self._required is None: return False return self._required @property def documentation(self): return self._help @property def cli_type_name(self): if self._cli_type_name is not None: return self._cli_type_name elif self._action in ['store_true', 'store_false']: return 'boolean' else: # Default to 'string' type if we don't have any # other info. return 'string' @property def cli_type(self): cli_type = str if self._action in ['store_true', 'store_false']: cli_type = bool return cli_type @property def choices(self): return self._choices @property def group_name(self): return self._group_name class CLIArgument(BaseCLIArgument): """Represents a CLI argument that maps to a service parameter. """ TYPE_MAP = { 'structure': str, 'map': str, 'timestamp': str, 'list': str, 'string': str, 'float': float, 'integer': str, 'long': int, 'boolean': bool, 'double': float, 'blob': str } def __init__(self, name, argument_object, operation_object): """ :type name: str :param name: The name of the argument in "cli" form (e.g. ``min-instances``). :type argument_object: ``botocore.parameter.Parameter`` :param argument_object: The parameter object to associate with this object. :type operation_object: ``botocore.operation.Operation`` :param operation_object: The operation object associated with this object. """ self._name = name self.argument_object = argument_object self.operation_object = operation_object @property def py_name(self): return self._name.replace('-', '_') @property def required(self): return self.argument_object.required @required.setter def required(self, value): self.argument_object.required = value @property def documentation(self): return self.argument_object.documentation @property def cli_type_name(self): return self.argument_object.type @property def cli_type(self): return self.TYPE_MAP.get(self.argument_object.type, str) def add_to_parser(self, parser): """ See the ``BaseCLIArgument.add_to_parser`` docs for more information. """ cli_name = self.cli_name parser.add_argument( cli_name, help=self.documentation, type=self.cli_type, required=self.required) def add_to_params(self, parameters, value): if value is None: return else: # This is a two step process. First is the process of converting # the command line value into a python value. Normally this is # handled by argparse directly, but there are cases where extra # processing is needed. For example, "--foo name=value" the value # can be converted from "name=value" to {"name": "value"}. This is # referred to as the "unpacking" process. Once we've unpacked the # argument value, we have to decide how this is converted into # something that can be consumed by botocore. Many times this is # just associating the key and value in the params dict as down # below. Sometimes this can be more complicated, and subclasses # can customize as they need. unpacked = self._unpack_argument(value) LOG.debug('Unpacked value of "%s" for parameter "%s": %s', value, self.argument_object.py_name, unpacked) parameters[self.argument_object.py_name] = unpacked def _unpack_argument(self, value): if not hasattr(self.argument_object, 'no_paramfile'): value = self._handle_param_file(value) service_name = self.operation_object.service.endpoint_prefix operation_name = xform_name(self.operation_object.name, '-') responses = self._emit('process-cli-arg.%s.%s' % ( service_name, operation_name), param=self.argument_object, value=value, operation=self.operation_object) override = first_non_none_response(responses) if override is not None: # A plugin supplied an alternate conversion, # use it instead. return override else: # Fall back to the default arg processing. return unpack_cli_arg(self.argument_object, value) def _handle_param_file(self, value): session = self.operation_object.service.session # If the arg is suppose to be a list type, just # get the first element in the list, as it may # refer to a file:// (or http/https) type. potential_param_value = value if isinstance(value, list) and len(value) == 1: potential_param_value = value[0] try: actual_value = get_paramfile(session, potential_param_value) except ResourceLoadingError as e: raise BadArgumentError( "Bad value for argument '%s': %s" % (self.cli_name, e)) if actual_value is not None: value = actual_value return value def _emit(self, name, **kwargs): session = self.operation_object.service.session return session.emit(name, **kwargs) class ListArgument(CLIArgument): def add_to_parser(self, parser): cli_name = self.cli_name parser.add_argument(cli_name, nargs='*', type=self.cli_type, required=self.required) class BooleanArgument(CLIArgument): """Represent a boolean CLI argument. A boolean parameter is specified without a value:: aws foo bar --enabled For cases wher the boolean parameter is required we need to add two parameters:: aws foo bar --enabled aws foo bar --no-enabled We use the capabilities of the CLIArgument to help achieve this. """ def __init__(self, name, argument_object, operation_object, action='store_true', dest=None, group_name=None, default=None): super(BooleanArgument, self).__init__(name, argument_object, operation_object) self._mutex_group = None self._action = action if dest is None: self._destination = self.py_name else: self._destination = dest if group_name is None: self._group_name = self.name else: self._group_name = group_name self._default = default def add_to_params(self, parameters, value): # If a value was explicitly specified (so value is True/False # but *not* None) then we add it to the params dict. # If the value was not explicitly set (value is None) # we don't add it to the params dict. if value is not None: parameters[self.py_name] = value def add_to_arg_table(self, argument_table): # Boolean parameters are a bit tricky. For a single boolean parameter # we actually want two CLI params, a --foo, and a --no-foo. To do this # we need to add two entries to the argument table. So we can add # ourself as the positive option (--no), and then create a clone of # ourselves for the negative service. We then insert both into the # arg table. argument_table[self.name] = self negative_name = 'no-%s' % self.name negative_version = self.__class__(negative_name, self.argument_object, self.operation_object, action='store_false', dest=self._destination, group_name=self.group_name) argument_table[negative_name] = negative_version def add_to_parser(self, parser): parser.add_argument(self.cli_name, help=self.documentation, action=self._action, default=self._default, dest=self._destination) @property def group_name(self): return self._group_name awscli-1.2.9/awscli/clidocs.py0000644€BÈÀ´00000000000004116012254746560020055 0ustar jamessarwheel00000000000000# Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://aws.amazon.com/apache2.0/ # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. import logging from bcdoc.docevents import DOC_EVENTS from awscli import SCALAR_TYPES LOG = logging.getLogger(__name__) class CLIDocumentEventHandler(object): def __init__(self, help_command): self.help_command = help_command self.register(help_command.session, help_command.event_class) self.help_command.doc.translation_map = self.build_translation_map() self._arg_groups = self._build_arg_table_groups(help_command) self._documented_arg_groups = [] def _build_arg_table_groups(self, help_command): arg_groups = {} for name, arg in help_command.arg_table.items(): if arg.group_name is not None: arg_groups.setdefault(arg.group_name, []).append(arg) return arg_groups def build_translation_map(self): return dict() def _map_handlers(self, session, event_class, mapfn): for event in DOC_EVENTS: event_handler_name = event.replace('-', '_') if hasattr(self, event_handler_name): event_handler = getattr(self, event_handler_name) format_string = DOC_EVENTS[event] num_args = len(format_string.split('.')) - 2 format_args = (event_class,) + ('*',) * num_args event_string = event + format_string % format_args unique_id = event_class + event_handler_name mapfn(event_string, event_handler, unique_id) def register(self, session, event_class): """ The default register iterates through all of the available document events and looks for a corresponding handler method defined in the object. If it's there, that handler method will be registered for the all events of that type for the specified ``event_class``. """ self._map_handlers(session, event_class, session.register) def unregister(self): """ The default unregister iterates through all of the available document events and looks for a corresponding handler method defined in the object. If it's there, that handler method will be unregistered for the all events of that type for the specified ``event_class``. """ self._map_handlers(self.help_command.session, self.help_command.event_class, self.help_command.session.unregister) # These are default doc handlers that apply in the general case. def doc_title(self, help_command, **kwargs): doc = help_command.doc doc.style.h1(help_command.name) def doc_description(self, help_command, **kwargs): doc = help_command.doc doc.style.h2('Description') doc.include_doc_string(help_command.description) doc.style.new_paragraph() def doc_synopsis_start(self, help_command, **kwargs): self._documented_arg_groups = [] doc = help_command.doc doc.style.h2('Synopsis') doc.style.start_codeblock() doc.writeln('%s' % help_command.name) def doc_synopsis_option(self, arg_name, help_command, **kwargs): doc = help_command.doc argument = help_command.arg_table[arg_name] if argument.group_name in self._arg_groups: if argument.group_name in self._documented_arg_groups: # This arg is already documented so we can move on. return option_str = ' | '.join( [a.cli_name for a in self._arg_groups[argument.group_name]]) self._documented_arg_groups.append(argument.group_name) else: option_str = '%s ' % argument.cli_name if not argument.required: option_str = '[%s]' % option_str doc.writeln('%s' % option_str) def doc_synopsis_end(self, help_command, **kwargs): doc = help_command.doc doc.style.end_codeblock() # Reset the documented arg groups for other sections # that may document args (the detailed docs following # the synopsis). self._documented_arg_groups = [] def doc_options_start(self, help_command, **kwargs): doc = help_command.doc doc.style.h2('Options') if not help_command.arg_table: doc.write('*None*\n') def doc_option(self, arg_name, help_command, **kwargs): doc = help_command.doc argument = help_command.arg_table[arg_name] if argument.group_name in self._arg_groups: if argument.group_name in self._documented_arg_groups: # This arg is already documented so we can move on. return name = ' | '.join( ['``%s``' % a.cli_name for a in self._arg_groups[argument.group_name]]) self._documented_arg_groups.append(argument.group_name) else: name = '``%s``' % argument.cli_name doc.write('%s (%s)\n' % (name, argument.cli_type_name)) doc.style.indent() doc.include_doc_string(argument.documentation) doc.style.dedent() doc.style.new_paragraph() def doc_options_end(self, help_command, **kwargs): doc = help_command.doc operation = help_command.obj if hasattr(operation, 'filters'): doc.style.h2('Filters') sorted_names = sorted(operation.filters) for filter_name in sorted_names: filter_data = operation.filters[filter_name] doc.style.h3(filter_name) if 'documentation' in filter_data: doc.include_doc_string(filter_data['documentation']) if 'choices' in filter_data: doc.style.new_paragraph() doc.write('Valid Values: ') choices = '|'.join(filter_data['choices']) doc.write(choices) doc.style.new_paragraph() class ProviderDocumentEventHandler(CLIDocumentEventHandler): def doc_synopsis_start(self, help_command, **kwargs): doc = help_command.doc doc.style.h2('Synopsis') doc.style.codeblock(help_command.synopsis) doc.include_doc_string(help_command.help_usage) def doc_synopsis_option(self, arg_name, help_command, **kwargs): pass def doc_synopsis_end(self, help_command, **kwargs): doc = help_command.doc doc.style.new_paragraph() def doc_options_start(self, help_command, **kwargs): doc = help_command.doc doc.style.h2('Options') def doc_option(self, arg_name, help_command, **kwargs): doc = help_command.doc argument = help_command.arg_table[arg_name] doc.writeln('``%s`` (%s)' % (argument.cli_name, argument.cli_type_name)) doc.include_doc_string(argument.documentation) if argument.choices: doc.style.start_ul() for choice in argument.choices: doc.style.li(choice) doc.style.end_ul() def doc_subitems_start(self, help_command, **kwargs): doc = help_command.doc doc.style.h2('Available Services') doc.style.toctree() def doc_subitem(self, command_name, help_command, **kwargs): doc = help_command.doc file_name = '%s/index' % command_name doc.style.tocitem(command_name, file_name=file_name) class ServiceDocumentEventHandler(CLIDocumentEventHandler): def build_translation_map(self): d = {} for op in self.help_command.obj.operations: d[op.name] = op.cli_name return d # A service document has no synopsis. def doc_synopsis_start(self, help_command, **kwargs): pass def doc_synopsis_option(self, arg_name, help_command, **kwargs): pass def doc_synopsis_end(self, help_command, **kwargs): pass # A service document has no option section. def doc_options_start(self, help_command, **kwargs): pass def doc_option(self, arg_name, help_command, **kwargs): pass def doc_option_example(self, arg_name, help_command, **kwargs): pass def doc_options_end(self, help_command, **kwargs): pass def doc_title(self, help_command, **kwargs): doc = help_command.doc doc.style.h1(help_command.name) def doc_description(self, help_command, **kwargs): doc = help_command.doc service = help_command.obj doc.style.h2('Description') doc.include_doc_string(service.documentation) def doc_subitems_start(self, help_command, **kwargs): doc = help_command.doc doc.style.h2('Available Commands') doc.style.toctree() def doc_subitem(self, command_name, help_command, **kwargs): doc = help_command.doc doc.style.tocitem(command_name) class OperationDocumentEventHandler(CLIDocumentEventHandler): def build_translation_map(self): LOG.debug('build_translation_map') operation = self.help_command.obj d = {} for param in operation.params: d[param.name] = param.cli_name for operation in operation.service.operations: d[operation.name] = operation.cli_name return d def doc_breadcrumbs(self, help_command, event_name, **kwargs): doc = help_command.doc if doc.target != 'man': l = event_name.split('.') if len(l) > 1: service_name = l[1] doc.write('[ ') doc.style.ref('aws', '../index') doc.write(' . ') doc.style.ref(service_name, 'index') doc.write(' ]') def doc_title(self, help_command, **kwargs): doc = help_command.doc doc.style.h1(help_command.name) def doc_description(self, help_command, **kwargs): doc = help_command.doc operation = help_command.obj doc.style.h2('Description') doc.include_doc_string(operation.documentation) def _json_example_value_name(self, param): if param.type == 'string': if hasattr(param, 'enum'): choices = param.enum return '|'.join(['"%s"' % c for c in choices]) else: return '"string"' elif param.type == 'boolean': return 'true|false' else: return '%s' % param.type def _json_example(self, doc, param): if param.type == 'list': doc.write('[') if param.members.type in SCALAR_TYPES: doc.write('%s, ...' % self._json_example_value_name(param.members)) else: doc.style.indent() doc.style.new_line() self._json_example(doc, param.members) doc.style.new_line() doc.write('...') doc.style.dedent() doc.style.new_line() doc.write(']') elif param.type == 'map': doc.write('{') doc.style.indent() key_string = self._json_example_value_name(param.keys) doc.write('%s: ' % key_string) if param.members.type in SCALAR_TYPES: doc.write(self._json_example_value_name(param.members)) else: doc.style.indent() self._json_example(doc, param.members) doc.style.dedent() doc.style.new_line() doc.write('...') doc.style.dedent() doc.write('}') elif param.type == 'structure': doc.write('{') doc.style.indent() doc.style.new_line() for i, member in enumerate(param.members): if member.type in SCALAR_TYPES: doc.write('"%s": %s' % (member.name, self._json_example_value_name(member))) elif member.type == 'structure': doc.write('"%s": ' % member.name) self._json_example(doc, member) elif member.type == 'map': doc.write('"%s": ' % member.name) self._json_example(doc, member) elif member.type == 'list': doc.write('"%s": ' % member.name) self._json_example(doc, member) if i < len(param.members) - 1: doc.write(',') doc.style.new_line() else: doc.style.dedent() doc.style.new_line() doc.write('}') def doc_option_example(self, arg_name, help_command, **kwargs): doc = help_command.doc argument = help_command.arg_table[arg_name] if argument.group_name in self._arg_groups: if argument.group_name in self._documented_arg_groups: # Args with group_names (boolean args) don't # need to generate example syntax. return param = argument.argument_object if param and param.example_fn: # TODO: bcdoc should not know about shorthand syntax. This # should be pulled out into a separate handler in the # awscli.customizations package. example_syntax = param.example_fn(param) if example_syntax is None: # If the shorthand syntax returns a value of None, # this indicates to us that there is no example # needed for this param so we can immediately # return. return doc.style.new_paragraph() doc.write('Shorthand Syntax') doc.style.start_codeblock() for example_line in example_syntax.splitlines(): doc.writeln(example_line) doc.style.end_codeblock() if param is not None and param.type == 'list' and \ param.members.type in SCALAR_TYPES: # A list of scalars is special. While you *can* use # JSON ( ["foo", "bar", "baz"] ), you can also just # use the argparse behavior of space separated lists. # "foo" "bar" "baz". In fact we don't even want to # document the JSON syntax in this case. doc.style.new_paragraph() doc.write('Syntax') doc.style.start_codeblock() example_type = self._json_example_value_name(param.members) doc.write('%s %s ...' % (example_type, example_type)) doc.style.end_codeblock() doc.style.new_paragraph() elif argument.cli_type_name not in SCALAR_TYPES: doc.style.new_paragraph() doc.write('JSON Syntax') doc.style.start_codeblock() self._json_example(doc, param) doc.style.end_codeblock() doc.style.new_paragraph() def _doc_member(self, doc, member_name, member): docs = member.get('documentation', '') if member_name: doc.write('%s -> (%s)' % (member_name, member['type'])) else: doc.write('(%s)' % member['type']) doc.style.indent() doc.style.new_paragraph() doc.include_doc_string(docs) doc.style.new_paragraph() if member['type'] == 'structure': for sub_name in member['members']: sub_member = member['members'][sub_name] self._doc_member(doc, sub_name, sub_member) elif member['type'] == 'map': keys = member['keys'] self._doc_member(doc, keys.get('xmlname', 'key'), keys) members = member['members'] self._doc_member(doc, members.get('xmlname', 'value'), members) elif member['type'] == 'list': self._doc_member(doc, '', member['members']) doc.style.dedent() doc.style.new_paragraph() def doc_output(self, help_command, event_name, **kwargs): doc = help_command.doc doc.style.h2('Output') operation = help_command.obj output = operation.output if output is None: doc.write('None') else: for member_name in output['members']: member = output['members'][member_name] self._doc_member(doc, member_name, member) awscli-1.2.9/awscli/clidriver.py0000644€BÈÀ´00000000000005147212254746560020427 0ustar jamessarwheel00000000000000# Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://aws.amazon.com/apache2.0/ # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. import sys import logging import botocore.session from botocore import __version__ as botocore_version from botocore.hooks import HierarchicalEmitter from botocore import xform_name from botocore.compat import copy_kwargs, OrderedDict from botocore.exceptions import NoCredentialsError from botocore.exceptions import NoRegionError from awscli import EnvironmentVariables, __version__ from awscli.formatter import get_formatter from awscli.plugin import load_plugins from awscli.argparser import MainArgParser from awscli.argparser import ServiceArgParser from awscli.argparser import ArgTableArgParser from awscli.help import ProviderHelpCommand from awscli.help import ServiceHelpCommand from awscli.help import OperationHelpCommand from awscli.arguments import CustomArgument from awscli.arguments import ListArgument from awscli.arguments import BooleanArgument from awscli.arguments import CLIArgument from awscli.arguments import UnknownArgumentError LOG = logging.getLogger('awscli.clidriver') def main(): driver = create_clidriver() return driver.main() def create_clidriver(): emitter = HierarchicalEmitter() session = botocore.session.Session(EnvironmentVariables, emitter) _set_user_agent_for_session(session) load_plugins(session.full_config.get('plugins', {}), event_hooks=emitter) driver = CLIDriver(session=session) return driver def _set_user_agent_for_session(session): session.user_agent_name = 'aws-cli' session.user_agent_version = __version__ class CLIDriver(object): def __init__(self, session=None): if session is None: self.session = botocore.session.get_session(EnvironmentVariables) _set_user_agent_for_session(self.session) else: self.session = session self._cli_data = None self._command_table = None self._argument_table = None def _get_cli_data(self): # Not crazy about this but the data in here is needed in # several places (e.g. MainArgParser, ProviderHelp) so # we load it here once. if self._cli_data is None: self._cli_data = self.session.get_data('cli') return self._cli_data def _get_command_table(self): if self._command_table is None: self._command_table = self._build_command_table() return self._command_table def _get_argument_table(self): if self._argument_table is None: self._argument_table = self._build_argument_table() return self._argument_table def _build_command_table(self): """ Create the main parser to handle the global arguments. :rtype: ``argparser.ArgumentParser`` :return: The parser object """ command_table = self._build_builtin_commands(self.session) self.session.emit('building-command-table.main', command_table=command_table, session=self.session) return command_table def _build_builtin_commands(self, session): commands = OrderedDict() services = session.get_available_services() for service_name in services: commands[service_name] = ServiceCommand(cli_name=service_name, session=self.session, service_name=service_name) return commands def _build_argument_table(self): argument_table = OrderedDict() cli_data = self._get_cli_data() cli_arguments = cli_data.get('options', None) for option in cli_arguments: option_params = copy_kwargs(cli_arguments[option]) # Special case the 'choices' param. Allows choices # to reference a variable from the session. if 'choices' in option_params: choices = option_params['choices'] if not isinstance(choices, list): # Assume it's a reference like # "{provider}/_regions", so first resolve # the provider. provider = self.session.get_variable('provider') # The grab the var from the session choices_path = choices.format(provider=provider) choices = list(self.session.get_data(choices_path)) option_params['choices'] = choices argument_object = self._create_argument_object(option, option_params) argument_object.add_to_arg_table(argument_table) # Then the final step is to send out an event so handlers # can add extra arguments or modify existing arguments. self.session.emit('building-top-level-params', argument_table=argument_table) return argument_table def _create_argument_object(self, option_name, option_params): return CustomArgument( option_name, help_text=option_params.get('help', ''), dest=option_params.get('dest'),default=option_params.get('default'), action=option_params.get('action'), required=option_params.get('required'), choices=option_params.get('choices')) def create_help_command(self): cli_data = self._get_cli_data() return ProviderHelpCommand(self.session, self._get_command_table(), self._get_argument_table(), cli_data.get('description', None), cli_data.get('synopsis', None), cli_data.get('help_usage', None)) def _create_parser(self): # Also add a 'help' command. command_table = self._get_command_table() command_table['help'] = self.create_help_command() cli_data = self._get_cli_data() parser = MainArgParser( command_table, self.session.user_agent(), cli_data.get('description', None), cli_data.get('synopsis', None), self._get_argument_table()) return parser def main(self, args=None): """ :param args: List of arguments, with the 'aws' removed. For example, the command "aws s3 list-objects --bucket foo" will have an args list of ``['s3', 'list-objects', '--bucket', 'foo']``. """ if args is None: args = sys.argv[1:] parser = self._create_parser() command_table = self._get_command_table() parsed_args, remaining = parser.parse_known_args(args) self._handle_top_level_args(parsed_args) try: return command_table[parsed_args.command](remaining, parsed_args) except UnknownArgumentError as e: sys.stderr.write(str(e) + '\n') return 255 except NoRegionError as e: msg = ('%s You can also configure your region by running ' '"aws configure".' % e) self._show_error(msg) return 255 except NoCredentialsError as e: msg = ('%s. You can configure credentials by running ' '"aws configure".' % e) self._show_error(msg) return 255 except Exception as e: LOG.debug("Exception caught in main()", exc_info=True) LOG.debug("Exiting with rc 255") sys.stderr.write("%s\n" % e) return 255 def _show_error(self, msg): LOG.debug(msg, exc_info=True) sys.stderr.write(msg) sys.stderr.write('\n') def _handle_top_level_args(self, args): self.session.emit('top-level-args-parsed', parsed_args=args) if args.profile: self.session.profile = args.profile if args.debug: # TODO: # Unfortunately, by setting debug mode here, we miss out # on all of the debug events prior to this such as the # loading of plugins, etc. self.session.set_debug_logger(logger_name='botocore') self.session.set_debug_logger(logger_name='awscli') LOG.debug("CLI version: %s, botocore version: %s", self.session.user_agent(), botocore_version) else: self.session.set_stream_logger(logger_name='awscli', log_level=logging.ERROR) class CLICommand(object): """Interface for a CLI command. This class represents a top level CLI command (``aws ec2``, ``aws s3``, ``aws config``). """ @property def name(self): # Subclasses must implement a name. raise NotImplementedError("name") @name.setter def name(self, value): # Subclasses must implement setting/changing the cmd name. raise NotImplementedError("name") def __call__(self, args, parsed_globals): """Invoke CLI operation. :type args: str :param args: The remaining command line args. :type parsed_globals: ``argparse.Namespace`` :param parsed_globals: The parsed arguments so far. :rtype: int :return: The return code of the operation. This will be used as the RC code for the ``aws`` process. """ # Subclasses are expected to implement this method. pass def create_help_command(self): # Subclasses are expected to implement this method if they want # help docs. return None @property def arg_table(self): return {} class ServiceCommand(CLICommand): """A service command for the CLI. For example, ``aws ec2 ...`` we'd create a ServiceCommand object that represents the ec2 service. """ def __init__(self, cli_name, session, service_name=None): # The cli_name is the name the user types, the name we show # in doc, etc. # The service_name is the name we used internally with botocore. # For example, we have the 's3api' as the cli_name for the service # but this is actually bound to the 's3' service name in botocore, # i.e. we load s3.json from the botocore data dir. Most of # the time these are the same thing but in the case of renames, # we want users/external things to be able to rename the cli name # but *not* the service name, as this has to be exactly what # botocore expects. self._name = cli_name self.session = session self._command_table = None self._service_object = None if service_name is None: # Then default to using the cli name. self._service_name = cli_name else: self._service_name = service_name @property def name(self): return self._name @name.setter def name(self, value): self._name = value def _get_command_table(self): if self._command_table is None: self._command_table = self._create_command_table() return self._command_table def _get_service_object(self): if self._service_object is None: self._service_object = self.session.get_service(self._service_name) return self._service_object def __call__(self, args, parsed_globals): # Once we know we're trying to call a service for this operation # we can go ahead and create the parser for it. We # can also grab the Service object from botocore. service_parser = self._create_parser() parsed_args, remaining = service_parser.parse_known_args(args) command_table = self._get_command_table() return command_table[parsed_args.operation](remaining, parsed_globals) def _create_command_table(self): command_table = OrderedDict() service_object = self._get_service_object() for operation_object in service_object.operations: cli_name = xform_name(operation_object.name, '-') command_table[cli_name] = ServiceOperation( name=cli_name, parent_name=self._name, operation_object=operation_object, operation_caller=CLIOperationCaller(self.session), service_object=service_object) self.session.emit('building-command-table.%s' % self._name, command_table=command_table, session=self.session) return command_table def create_help_command(self): command_table = self._get_command_table() service_object = self._get_service_object() return ServiceHelpCommand(session=self.session, obj=service_object, command_table=command_table, arg_table=None, event_class='Operation', name=self._name) def _create_parser(self): command_table = self._get_command_table() # Also add a 'help' command. command_table['help'] = self.create_help_command() return ServiceArgParser( operations_table=command_table, service_name=self._name) class ServiceOperation(object): """A single operation of a service. This class represents a single operation for a service, for example ``ec2.DescribeInstances``. """ ARG_TYPES = { 'list': ListArgument, 'boolean': BooleanArgument, } DEFAULT_ARG_CLASS = CLIArgument def __init__(self, name, parent_name, operation_object, operation_caller, service_object): """ :type name: str :param name: The name of the operation/subcommand. :type parent_name: str :param parent_name: The name of the parent command. :type operation_object: ``botocore.operation.Operation`` :param operation_object: The operation associated with this subcommand. :type operation_caller: ``CLIOperationCaller`` :param operation_caller: An object that can properly call the operation. :type service_object: ``botocore.service.Service`` :param service_object: The service associated wtih the object. """ self._arg_table = None self._name = name # These is used so we can figure out what the proper event # name should be .. self._parent_name = parent_name self._operation_object = operation_object self._operation_caller = operation_caller self._service_object = service_object @property def arg_table(self): if self._arg_table is None: self._arg_table = self._create_argument_table() return self._arg_table def __call__(self, args, parsed_globals): # Once we know we're trying to call a particular operation # of a service we can go ahead and load the parameters. operation_parser = self._create_operation_parser(self.arg_table) self._add_help(operation_parser) parsed_args, remaining = operation_parser.parse_known_args(args) if parsed_args.help == 'help': op_help = self.create_help_command() return op_help(parsed_args, parsed_globals) elif parsed_args.help: remaining.append(parsed_args.help) if remaining: raise UnknownArgumentError( "Unknown options: %s" % ', '.join(remaining)) service_name = self._service_object.endpoint_prefix operation_name = self._operation_object.name event = 'operation-args-parsed.%s.%s' % (self._parent_name, self._name) self._emit(event, parsed_args=parsed_args) call_parameters = self._build_call_parameters(parsed_args, self.arg_table) return self._operation_caller.invoke( self._operation_object, call_parameters, parsed_globals) def create_help_command(self): return OperationHelpCommand( self._service_object.session, self._service_object, self._operation_object, arg_table=self.arg_table, name=self._name, event_class=self._parent_name) def _add_help(self, parser): # The 'help' output is processed a little differently from # the provider/operation help because the arg_table has # CLIArguments for values. parser.add_argument('help', nargs='?') def _build_call_parameters(self, args, arg_table): # We need to convert the args specified on the command # line as valid **kwargs we can hand to botocore. service_params = {} # args is an argparse.Namespace object so we're using vars() # so we can iterate over the parsed key/values. parsed_args = vars(args) for arg_name, arg_object in arg_table.items(): py_name = arg_object.py_name if py_name in parsed_args: arg_object.add_to_params(service_params, parsed_args[py_name]) return service_params def _create_argument_table(self): argument_table = OrderedDict() # Arguments are treated a differently than service and # operations. Instead of doing a get_parameter() we just # load all the parameter objects up front for the operation. # We could potentially do the same thing as service/operations # but botocore already builds all the parameter objects # when calling an operation so we'd have to optimize that first # before using get_parameter() in the cli would be advantageous for argument in self._operation_object.params: cli_arg_name = argument.cli_name[2:] arg_class = self.ARG_TYPES.get(argument.type, self.DEFAULT_ARG_CLASS) arg_object = arg_class(cli_arg_name, argument, self._operation_object) arg_object.add_to_arg_table(argument_table) LOG.debug(argument_table) service_name = self._service_object.endpoint_prefix operation_name = self._operation_object.name self._emit('building-argument-table.%s.%s' % (self._parent_name, self._name), operation=self._operation_object, argument_table=argument_table) return argument_table def _emit(self, name, **kwargs): session = self._service_object.session return session.emit(name, **kwargs) def _create_operation_parser(self, arg_table): parser = ArgTableArgParser(arg_table) return parser class CLIOperationCaller(object): """ Call an AWS operation and format the response. This class handles the non-error path. If an HTTP error occurs on the call to the service operation, it will be detected and handled by the :class:`awscli.errorhandler.ErrorHandler` which is registered on the ``after-call`` event. """ def __init__(self, session): self._session = session def invoke(self, operation_object, parameters, parsed_globals): # We could get an error from get_endpoint() about not having # a region configured. Before this happens we want to check # for credentials so we can give a good error message. if not self._session.get_credentials(): raise NoCredentialsError() endpoint = operation_object.service.get_endpoint( region_name=parsed_globals.region, endpoint_url=parsed_globals.endpoint_url) endpoint.verify = not parsed_globals.no_verify_ssl if operation_object.can_paginate and parsed_globals.paginate: pages = operation_object.paginate(endpoint, **parameters) self._display_response(operation_object, pages, parsed_globals) else: http_response, response_data = operation_object.call(endpoint, **parameters) self._display_response(operation_object, response_data, parsed_globals) return 0 def _display_response(self, operation, response, args): output = args.output if output is None: output = self._session.get_variable('output') formatter = get_formatter(output, args) formatter(operation, response) awscli-1.2.9/awscli/completer.py0000755€BÈÀ´00000000000001454212254746560020436 0ustar jamessarwheel00000000000000# Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved. # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # http://aws.amazon.com/apache2.0/ # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. import awscli.clidriver import sys import logging import copy LOG = logging.getLogger(__name__) class Completer(object): def __init__(self): self.driver = awscli.clidriver.create_clidriver() self.main_hc = self.driver.create_help_command() self.main_options = [n for n in self.main_hc.arg_table] self.cmdline = None self.point = None self.command_hc = None self.subcommand_hc = None self.command_name = None self.subcommand_name = None self.current_word = None self.previous_word = None self.non_options = None def _complete_option(self, option_name): if option_name == '--region': return self.driver.session.get_data('aws/_regions').keys() if option_name == '--endpoint-url': return [] if option_name == '--output': cli_data = self.driver.session.get_data('cli') return cli_data['options']['output']['choices'] if option_name == '--profile': return self.driver.session.available_profiles return [] def _complete_provider(self): retval = [] if self.current_word.startswith('-'): cw = self.current_word.lstrip('-') l = ['--' + n for n in self.main_options if n.startswith(cw)] retval = l elif self.current_word == 'aws': retval = self.main_hc.command_table.keys() else: # Otherwise, see if they have entered a partial command name retval = [n for n in self.main_hc.command_table if n.startswith(self.current_word)] return retval def _complete_command(self): retval = [] if self.current_word == self.command_name: if self.command_hc: retval = self.command_hc.command_table.keys() elif self.current_word.startswith('-'): retval = self._find_possible_options() else: # See if they have entered a partial command name if self.command_hc: retval = [n for n in self.command_hc.command_table if n.startswith(self.current_word)] return retval def _complete_subcommand(self): retval = [] if self.current_word == self.subcommand_name: retval = [] elif self.current_word.startswith('-'): retval = self._find_possible_options() return retval def _find_possible_options(self): all_options = copy.copy(self.main_options) if self.subcommand_hc: all_options = all_options + list(self.subcommand_hc.arg_table.keys()) for opt in self.options: # Look thru list of options on cmdline. If there are # options that have already been specified and they are # not the current word, remove them from list of possibles. if opt != self.current_word: stripped_opt = opt.lstrip('-') if stripped_opt in all_options: all_options.remove(stripped_opt) cw = self.current_word.lstrip('-') possibles = ['--' + n for n in all_options if n.startswith(cw)] if len(possibles) == 1 and possibles[0] == self.current_word: return self._complete_option(possibles[0]) return possibles def _process_command_line(self): # Process the command line and try to find: # - command_name # - subcommand_name # - words # - current_word # - previous_word # - non_options # - options self.command_name = None self.subcommand_name = None self.words = self.cmdline[0:self.point].split() self.current_word = self.words[-1] if len(self.words) >= 2: self.previous_word = self.words[-2] else: self.previous_word = None self.non_options = [w for w in self.words if not w.startswith('-')] self.options = [w for w in self.words if w.startswith('-')] # Look for a command name in the non_options for w in self.non_options: if w in self.main_hc.command_table: self.command_name = w cmd_obj = self.main_hc.command_table[self.command_name] self.command_hc = cmd_obj.create_help_command() if self.command_hc and self.command_hc.command_table: # Look for subcommand name for w in self.non_options: if w in self.command_hc.command_table: self.subcommand_name = w cmd_obj = self.command_hc.command_table[self.subcommand_name] self.subcommand_hc = cmd_obj.create_help_command() break break def complete(self, cmdline, point): self.cmdline = cmdline self.command_name = None if point is None: point = len(cmdline) self.point = point self._process_command_line() if not self.command_name: # If we didn't find any command names in the cmdline # lets try to complete provider options return self._complete_provider() if self.command_name and not self.subcommand_name: return self._complete_command() return self._complete_subcommand() def complete(cmdline, point): choices = Completer().complete(cmdline, point) print(' \n'.join(choices)) if __name__ == '__main__': if len(sys.argv) == 3: cmdline = sys.argv[1] point = int(sys.argv[2]) elif len(sys.argv) == 2: cmdline = sys.argv[1] else: print('usage: %s ' % sys.argv[0]) sys.exit(1) print(complete(cmdline, point)) awscli-1.2.9/awscli/customizations/0000755€BÈÀ´00000000000000000012254752016021145 5ustar jamessarwheel00000000000000awscli-1.2.9/awscli/customizations/__init__.py0000644€BÈÀ´00000000000000271412254746560023271 0ustar jamessarwheel00000000000000# Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://aws.amazon.com/apache2.0/ # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. """ Customizations ============== As we start to accumulate more and more of these *built-in* customizations we probably need to come up with some way to organize them and to make it easy to add them and register them. One idea I had was to place them all with a package like this. That at least keeps them all in one place. Each module in this package should contain a single customization (I think). To take it a step further, we could have each module define a couple of well-defined attributes: * ``EVENT`` would be a string containing the event that this customization needs to be registered with. Or, perhaps this should be a list of events? * ``handler`` is a callable that will be registered as the handler for the event. Using a convention like this, we could perhaps automatically discover all customizations and register them without having to manually edit ``handlers.py`` each time. """ awscli-1.2.9/awscli/customizations/addexamples.py0000644€BÈÀ´00000000000000347212254746560024023 0ustar jamessarwheel00000000000000# Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://aws.amazon.com/apache2.0/ # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. """ Add authored examples to MAN and HTML documentation --------------------------------------------------- This customization allows authored examples in ReST format to be inserted into the generated help for an Operation. To get this to work you need to: * Register the ``add_examples`` function below with the ``doc-examples.*.*`` event. * Create a file containing ReST format fragment with the examples. The file needs to be created in the ``examples/`` directory and needs to be named ``-.rst``. For example, ``examples/ec2/ec2-create-key-pair.rst``. """ import os import logging LOG = logging.getLogger(__name__) def add_examples(help_command, **kwargs): doc_path = os.path.join( os.path.dirname( os.path.dirname( os.path.abspath(__file__))), 'examples') doc_path = os.path.join(doc_path, help_command.event_class) file_name = '%s.rst' % (help_command.name) doc_path = os.path.join(doc_path, file_name) LOG.debug("Looking for example file at: %s", doc_path) if os.path.isfile(doc_path): help_command.doc.style.h2('Examples') fp = open(doc_path) for line in fp.readlines(): help_command.doc.write(line) awscli-1.2.9/awscli/customizations/argrename.py0000644€BÈÀ´00000000000000322712254746560023473 0ustar jamessarwheel00000000000000# Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://aws.amazon.com/apache2.0/ # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. """ """ from awscli.customizations import utils ARGUMENT_RENAMES = { # Mapping of original arg to renamed arg. # The key is ..argname # The first part of the key is used for event registration # so if you wanted to rename something for an entire service you # could say 'ec2.*.dry-run': 'renamed-arg-name', or if you wanted # to rename across all services you could say '*.*.dry-run': 'new-name'. 'ec2.create-image.no-no-reboot': 'reboot', 'ec2.*.no-egress': 'ingress', 'ec2.*.no-disable-api-termination': 'enable-api-termination', } def register_arg_renames(cli): for original, new_name in ARGUMENT_RENAMES.items(): event_portion, original_arg_name = original.rsplit('.', 1) cli.register('building-argument-table.%s' % event_portion, rename_arg(original_arg_name, new_name)) def rename_arg(original_arg_name, new_name): def _rename_arg(argument_table, **kwargs): if original_arg_name in argument_table: utils.rename_argument(argument_table, original_arg_name, new_name) return _rename_arg awscli-1.2.9/awscli/customizations/cloudtrail.py0000644€BÈÀ´00000000000003046112254751605023670 0ustar jamessarwheel00000000000000# Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://aws.amazon.com/apache2.0/ # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. import json import logging import sys from awscli.customizations.commands import BasicCommand from awscli.customizations.service import Service from botocore.vendored import requests LOG = logging.getLogger(__name__) S3_POLICY_TEMPLATE = 'policy/S3/AWSCloudTrail-S3BucketPolicy-2013-11-01.json' SNS_POLICY_TEMPLATE = 'policy/SNS/AWSCloudTrail-SnsTopicPolicy-2013-11-01.json' def initialize(cli): """ The entry point for CloudTrail high level commands. """ cli.register('building-command-table.cloudtrail', inject_commands) def inject_commands(command_table, session, **kwargs): """ Called when the CloudTrail command table is being built. Used to inject new high level commands into the command list. These high level commands must not collide with existing low-level API call names. """ command_table['create-subscription'] = CloudTrailSubscribe(session) command_table['update-subscription'] = CloudTrailUpdate(session) class CloudTrailSubscribe(BasicCommand): """ Subscribe/update a user account to CloudTrail, creating the required S3 bucket, the optional SNS topic, and starting the CloudTrail monitoring and logging. """ NAME = 'create-subscription' DESCRIPTION = ('Creates and configures the AWS resources necessary to use' ' CloudTrail, creates a trail using those resources, and ' 'turns on logging.') SYNOPSIS = ('aws cloudtrail create-subscription' ' (--s3-use-bucket|--s3-new-bucket) bucket-name' ' [--sns-new-topic topic-name]\n') ARG_TABLE = [ {'name': 'name', 'required': True, 'help_text': 'Cloudtrail name'}, {'name': 's3-new-bucket', 'help_text': 'Create a new S3 bucket with this name'}, {'name': 's3-use-bucket', 'help_text': 'Use an existing S3 bucket with this name'}, {'name': 's3-prefix', 'help_text': 'S3 object prefix'}, {'name': 'sns-new-topic', 'help_text': 'Create a new SNS topic with this name'}, {'name': 'include-global-service-events', 'help_text': 'Whether to include global service events'}, {'name': 's3-custom-policy', 'help_text': 'Optional URL to a custom S3 policy template'}, {'name': 'sns-custom-policy', 'help_text': 'Optional URL to a custom SNS policy template'} ] UPDATE = False def _run_main(self, args, parsed_globals): endpoint_args = { 'region_name': None, 'endpoint_url': None } if 'region' in parsed_globals: endpoint_args['region_name'] = parsed_globals.region if 'endpoint_url' in parsed_globals: endpoint_args['endpoint_url'] = parsed_globals.endpoint_url # Initialize services LOG.debug('Initializing S3, SNS and CloudTrail...') self.iam = Service('iam', session=self._session) self.s3 = Service('s3', endpoint_args['region_name'], session=self._session) self.sns = Service('sns', endpoint_args['region_name'], session=self._session) self.cloudtrail = Service('cloudtrail', endpoint_args=endpoint_args, session=self._session) # Run the command and report success self._call(args, parsed_globals) return 1 def _call(self, options, parsed_globals): """ Run the command. Calls various services based on input options and outputs the final CloudTrail configuration. """ gse = options.include_global_service_events if gse: if gse.lower() == 'true': gse = True elif gse.lower() == 'false': gse = False else: raise ValueError('You must pass either true or false to' ' --include-global-service-events.') bucket = options.s3_use_bucket if options.s3_new_bucket: bucket = options.s3_new_bucket if self.UPDATE and options.s3_prefix is None: # Prefix was not passed and this is updating the S3 bucket, # so let's find the existing prefix and use that if possible res = self.cloudtrail.DescribeTrails( trail_name_list=[options.name]) trail_info = res['trailList'][0] if 'S3KeyPrefix' in trail_info: LOG.debug('Setting S3 prefix to {0}'.format( trail_info['S3KeyPrefix'])) options.s3_prefix = trail_info['S3KeyPrefix'] self.setup_new_bucket(bucket, options.s3_prefix, options.s3_custom_policy) elif not bucket and not self.UPDATE: # No bucket was passed for creation. raise ValueError('You must pass either --s3-use-bucket or' ' --s3-new-bucket to create.') if options.sns_new_topic: try: topic_result = self.setup_new_topic(options.sns_new_topic, options.sns_custom_policy) except Exception: # Roll back any S3 bucket creation if options.s3_new_bucket: self.s3.DeleteBucket(bucket=options.s3_new_bucket) raise try: cloudtrail_config = self.upsert_cloudtrail_config( options.name, bucket, options.s3_prefix, options.sns_new_topic, gse ) except Exception: # Roll back any S3 bucket / SNS topic creations if options.s3_new_bucket: self.s3.DeleteBucket(bucket=options.s3_new_bucket) if options.sns_new_topic: self.sns.DeleteTopic(topic_arn=topic_result['TopicArn']) raise sys.stdout.write('CloudTrail configuration:\n{config}\n'.format( config=json.dumps(cloudtrail_config, indent=2))) if not self.UPDATE: # If the configure call command above completes then this should # have a really high chance of also completing self.start_cloudtrail(options.name) sys.stdout.write( 'Logs will be delivered to {bucket}:{prefix}\n'.format( bucket=bucket, prefix=options.s3_prefix)) def setup_new_bucket(self, bucket, prefix, policy_url=None): """ Creates a new S3 bucket with an appropriate policy to let CloudTrail write to the prefix path. """ sys.stdout.write( 'Setting up new S3 bucket {bucket}...\n'.format(bucket=bucket)) # Who am I? response = self.iam.GetUser() account_id = response['User']['Arn'].split(':')[4] # Clean up the prefix - it requires a trailing slash if set if prefix and not prefix.endswith('/'): prefix += '/' # Fetch policy data from S3 or a custom URL if policy_url: policy = requests.get(policy_url).text else: data = self.s3.GetObject(bucket='awscloudtrail', key=S3_POLICY_TEMPLATE) policy = data['Body'].read() policy = policy.replace('', bucket)\ .replace('', account_id) if '/' in policy: policy = policy.replace('/', prefix or '') else: policy = policy.replace('', prefix or '') LOG.debug('Bucket policy:\n{0}'.format(policy)) # Make sure bucket doesn't already exist # Warn but do not fail if ListBucket permissions # are missing from the IAM role try: buckets = self.s3.ListBuckets()['Buckets'] except Exception: buckets = [] LOG.warn('Unable to list buckets, continuing...') if [b for b in buckets if b['Name'] == bucket]: raise Exception('Bucket {bucket} already exists.'.format( bucket=bucket)) data = self.s3.CreateBucket(bucket=bucket) try: self.s3.PutBucketPolicy(bucket=bucket, policy=policy) except Exception: # Roll back bucket creation self.s3.DeleteBucket(bucket=bucket) raise return data def setup_new_topic(self, topic, policy_url=None): """ Creates a new SNS topic with an appropriate policy to let CloudTrail post messages to the topic. """ sys.stdout.write( 'Setting up new SNS topic {topic}...\n'.format(topic=topic)) # Who am I? response = self.iam.GetUser() account_id = response['User']['Arn'].split(':')[4] # Make sure topic doesn't already exist # Warn but do not fail if ListTopics permissions # are missing from the IAM role? try: topics = self.sns.ListTopics()['Topics'] except Exception: topics = [] LOG.warn('Unable to list topics, continuing...') if [t for t in topics if t['TopicArn'].split(':')[-1] == topic]: raise Exception('Topic {topic} already exists.'.format( topic=topic)) region = self.sns.endpoint.region_name if policy_url: policy = requests.get(policy_url).text else: data = self.s3.GetObject(bucket='awscloudtrail', key=SNS_POLICY_TEMPLATE) policy = data['Body'].read() policy = policy.replace('', region)\ .replace('', account_id)\ .replace('', topic) LOG.debug('Bucket policy:\n{0}'.format(policy)) topic_result = self.sns.CreateTopic(name=topic) try: self.sns.SetTopicAttributes(topic_arn=topic_result['TopicArn'], attribute_name='Policy', attribute_value=policy) except Exception: # Roll back topic creation self.sns.DeleteTopic(topic_arn=topic_result['TopicArn']) raise return topic_result def upsert_cloudtrail_config(self, name, bucket, prefix, topic, gse): """ Either create or update the CloudTrail configuration depending on whether this command is a create or update command. """ sys.stdout.write('Creating/updating CloudTrail configuration...\n') config = { 'name': name } if bucket is not None: config['s3_bucket_name'] = bucket if prefix is not None: config['s3_key_prefix'] = prefix if topic is not None: config['sns_topic_name'] = topic if gse is not None: config['include_global_service_events'] = gse if not self.UPDATE: self.cloudtrail.CreateTrail(**config) else: self.cloudtrail.UpdateTrail(**config) return self.cloudtrail.DescribeTrails() def start_cloudtrail(self, name): """ Start the CloudTrail service, which begins logging. """ sys.stdout.write('Starting CloudTrail service...\n') return self.cloudtrail.StartLogging(name=name) class CloudTrailUpdate(CloudTrailSubscribe): """ Like subscribe above, but the update version of the command. """ NAME = 'update-subscription' UPDATE = True DESCRIPTION = ('Updates any of the trail configuration settings, and' ' creates and configures any new AWS resources specified.') SYNOPSIS = ('aws cloudtrail update-subscription' ' [(--s3-use-bucket|--s3-new-bucket) bucket-name]' ' [--sns-new-topic topic-name]\n') awscli-1.2.9/awscli/customizations/commands.py0000644€BÈÀ´00000000000001740712254746560023340 0ustar jamessarwheel00000000000000import bcdoc.docevents from botocore.compat import OrderedDict from awscli.clidocs import CLIDocumentEventHandler from awscli.argparser import ArgTableArgParser from awscli.clidriver import CLICommand from awscli.arguments import CustomArgument from awscli.help import HelpCommand class BasicCommand(CLICommand): """Basic top level command with no subcommands. If you want to create a new command, subclass this and provide the values documented below. """ # This is the name of your command, so if you want to # create an 'aws mycommand ...' command, the NAME would be # 'mycommand' NAME = 'commandname' # This is the description that will be used for the 'help' # command. DESCRIPTION = 'describe the command' # This is optional, if you are fine with the default synopsis # (the way all the built in operations are documented) then you # can leave this empty. SYNOPSIS = '' # If you want to provide some hand written examples, you can do # so here. This is written in RST format. This is optional, # you don't have to provide any examples, though highly encouraged! EXAMPLES = '' # If your command has arguments, you can specify them here. This is # somewhat of an implementation detail, but this is a list of dicts # where the dicts match the kwargs of the CustomArgument's __init__. # For example, if I want to add a '--argument-one' and an # '--argument-two' command, I'd say: # # ARG_TABLE = [ # {'name': 'argument-one', 'help_text': 'This argument does foo bar.', # 'action': 'store', 'required': False, 'cli_type_name': 'string',}, # {'name': 'argument-two', 'help_text': 'This argument does some other thing.', # 'action': 'store', 'choices': ['a', 'b', 'c']}, # ] ARG_TABLE = [] # If you want the command to have subcommands, you can provide a list of # dicts. We use a list here because we want to allow a user to provide # the order they want to use for subcommands. # SUBCOMMANDS = [ # {'name': 'subcommand1', 'command_class': SubcommandClass}, # {'name': 'subcommand2', 'command_class': SubcommandClass2}, # ] # The command_class must subclass from ``BasicCommand``. SUBCOMMANDS = [] # At this point, the only other thing you have to implement is a _run_main # method (see the method for more information). def __init__(self, session): self._session = session def __call__(self, args, parsed_globals): # args is the remaining unparsed args. # We might be able to parse these args so we need to create # an arg parser and parse them. subcommand_table = self._build_subcommand_table() parser = ArgTableArgParser(self.arg_table, subcommand_table) parsed_args, remaining = parser.parse_known_args(args) if hasattr(parsed_args, 'help'): self._display_help(parsed_args, parsed_globals) elif getattr(parsed_args, 'subcommand', None) is None: # No subcommand was specified so call the main # function for this top level command. self._run_main(parsed_args, parsed_globals) else: subcommand_table[parsed_args.subcommand](remaining, parsed_globals) def _run_main(self, parsed_args, parsed_globals): # Subclasses should implement this method. # parsed_globals are the parsed global args (things like region, # profile, output, etc.) # parsed_args are any arguments you've defined in your ARG_TABLE # that are parsed. These will come through as whatever you've # provided as the 'dest' key. Otherwise they default to the # 'name' key. For example: ARG_TABLE[0] = {"name": "foo-arg", ...} # can be accessed by ``parsed_args.foo_arg``. raise NotImplementedError("_run_main") def _build_subcommand_table(self): subcommand_table = OrderedDict() for subcommand in self.SUBCOMMANDS: subcommand_name = subcommand['name'] subcommand_class = subcommand['command_class'] subcommand_table[subcommand_name] = subcommand_class(self._session) self._session.emit('building-command-table.%s' % self.NAME, command_table=subcommand_table, session=self._session) return subcommand_table def _display_help(self, parsed_args, parsed_globals): help_command = self.create_help_command() help_command(parsed_args, parsed_globals) def create_help_command(self): return BasicHelp(self._session, self, command_table={}, arg_table=self.arg_table) @property def arg_table(self): arg_table = {} for arg_data in self.ARG_TABLE: custom_argument = CustomArgument(**arg_data) arg_table[arg_data['name']] = custom_argument return arg_table @classmethod def add_command(cls, command_table, session, **kwargs): command_table[cls.NAME] = cls(session) class BasicHelp(HelpCommand): event_class = 'command' def __init__(self, session, command_object, command_table, arg_table, event_handler_class=None): super(BasicHelp, self).__init__(session, command_object, command_table, arg_table) # This is defined in HelpCommand so we're matching the # casing here. if event_handler_class is None: event_handler_class=BasicDocHandler self.EventHandlerClass = event_handler_class # These are public attributes that are mapped from the command # object. These are used by the BasicDocHandler below. self.description = command_object.DESCRIPTION self.synopsis = command_object.SYNOPSIS self.examples = command_object.EXAMPLES @property def name(self): return self.obj.NAME def __call__(self, args, parsed_globals): # Create an event handler for a Provider Document instance = self.EventHandlerClass(self) # Now generate all of the events for a Provider document. # We pass ourselves along so that we can, in turn, get passed # to all event handlers. bcdoc.docevents.generate_events(self.session, self) self.renderer.render(self.doc.getvalue()) instance.unregister() class BasicDocHandler(CLIDocumentEventHandler): def __init__(self, help_command): super(BasicDocHandler, self).__init__(help_command) self.doc = help_command.doc def doc_description(self, help_command, **kwargs): self.doc.style.h2('Description') self.doc.write(help_command.description) self.doc.style.new_paragraph() def doc_synopsis_start(self, help_command, **kwargs): if not help_command.synopsis: super(BasicDocHandler, self).doc_synopsis_start( help_command=help_command, **kwargs) else: self.doc.style.h2('Synopsis') self.doc.style.start_codeblock() self.doc.writeln(help_command.synopsis) def doc_synopsis_end(self, help_command, **kwargs): if not help_command.synopsis: super(BasicDocHandler, self).doc_synopsis_end( help_command=help_command, **kwargs) else: self.doc.style.end_codeblock() def doc_option_example(self, arg_name, help_command, **kwargs): pass def doc_examples(self, help_command, **kwargs): if help_command.examples: self.doc.style.h2('Examples') self.doc.write(help_command.examples) def doc_subitems_start(self, help_command, **kwargs): pass def doc_subitem(self, command_name, help_command, **kwargs): pass def doc_subitems_end(self, help_command, **kwargs): pass awscli-1.2.9/awscli/customizations/configure.py0000644€BÈÀ´00000000000003472512254746560023522 0ustar jamessarwheel00000000000000# Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://aws.amazon.com/apache2.0/ # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. import os import re import sys import logging import six from botocore.exceptions import ProfileNotFound from awscli.customizations.commands import BasicCommand try: raw_input = raw_input except NameError: raw_input = input logger = logging.getLogger(__name__) NOT_SET = '' def register_configure_cmd(cli): cli.register('building-command-table.main', ConfigureCommand.add_command) class ConfigValue(object): def __init__(self, value, config_type, config_variable): self.value = value self.config_type = config_type self.config_variable = config_variable def mask_value(self): if self.value is NOT_SET: return self.value = _mask_value(self.value) class SectionNotFoundError(Exception): pass def _mask_value(current_value): if current_value is None: return 'None' else: return ('*' * 16) + current_value[-4:] class InteractivePrompter(object): def get_value(self, current_value, config_name, prompt_text=''): if config_name in ('aws_access_key_id', 'aws_secret_access_key'): current_value = _mask_value(current_value) response = raw_input("%s [%s]: " % (prompt_text, current_value)) if not response: # If the user hits enter, we return a value of None # instead of an empty string. That way we can determine # whether or not a value has changed. response = None return response class ConfigFileWriter(object): SECTION_REGEX = re.compile(r'\[(?P
[^]]+)\]') OPTION_REGEX = re.compile( r'(?P