diff -Nru awscli-1.9.9/awscli/arguments.py awscli-1.10.1/awscli/arguments.py --- awscli-1.9.9/awscli/arguments.py 2015-11-24 01:59:32.000000000 +0000 +++ awscli-1.10.1/awscli/arguments.py 2016-01-28 22:38:12.000000000 +0000 @@ -392,6 +392,7 @@ self._required = is_required self._operation_model = operation_model self._event_emitter = event_emitter + self._documentation = argument_model.documentation @property def py_name(self): @@ -407,7 +408,11 @@ @property def documentation(self): - return self.argument_model.documentation + return self._documentation + + @documentation.setter + def documentation(self, value): + self._documentation = value @property def cli_type_name(self): diff -Nru awscli-1.9.9/awscli/clidriver.py awscli-1.10.1/awscli/clidriver.py --- awscli-1.9.9/awscli/clidriver.py 2015-11-24 01:59:32.000000000 +0000 +++ awscli-1.10.1/awscli/clidriver.py 2016-01-28 22:38:12.000000000 +0000 @@ -21,7 +21,6 @@ from botocore.compat import copy_kwargs, OrderedDict from botocore.exceptions import NoCredentialsError from botocore.exceptions import NoRegionError -from botocore.client import Config from awscli import EnvironmentVariables, __version__ from awscli.formatter import get_formatter @@ -658,15 +657,10 @@ value is returned. """ - if parsed_globals.read_timeout is not None: - config = Config(read_timeout=parsed_globals.read_timeout) - else: - config = Config() client = self._session.create_client( service_name, region_name=parsed_globals.region, endpoint_url=parsed_globals.endpoint_url, - verify=parsed_globals.verify_ssl, - config=config) + verify=parsed_globals.verify_ssl) py_operation_name = xform_name(operation_name) if client.can_paginate(py_operation_name) and parsed_globals.paginate: paginator = client.get_paginator(py_operation_name) diff -Nru awscli-1.9.9/awscli/customizations/argrename.py awscli-1.10.1/awscli/customizations/argrename.py --- awscli-1.9.9/awscli/customizations/argrename.py 2015-11-24 01:59:32.000000000 +0000 +++ awscli-1.10.1/awscli/customizations/argrename.py 2016-01-28 22:38:12.000000000 +0000 @@ -42,6 +42,9 @@ 'codepipeline.get-pipeline.version': 'pipeline-version', 'codepipeline.create-custom-action-type.version': 'action-version', 'codepipeline.delete-custom-action-type.version': 'action-version', + 'route53.delete-traffic-policy.version': 'traffic-policy-version', + 'route53.get-traffic-policy.version': 'traffic-policy-version', + 'route53.update-traffic-policy-comment.version': 'traffic-policy-version' } # Same format as ARGUMENT_RENAMES, but instead of renaming the arguments, diff -Nru awscli-1.9.9/awscli/customizations/arguments.py awscli-1.10.1/awscli/customizations/arguments.py --- awscli-1.9.9/awscli/customizations/arguments.py 2015-11-24 01:59:32.000000000 +0000 +++ awscli-1.10.1/awscli/customizations/arguments.py 2016-01-28 22:38:13.000000000 +0000 @@ -15,7 +15,6 @@ from awscli.arguments import CustomArgument import jmespath - def resolve_given_outfile_path(path): """Asserts that a path is writable and returns the expanded path""" if path is None: @@ -90,11 +89,12 @@ class QueryOutFileArgument(StatefulArgument): """An argument that write a JMESPath query result to a file""" - def __init__(self, session, name, query, after_call_event, + def __init__(self, session, name, query, after_call_event, perm, *args, **kwargs): self._session = session self._query = query self._after_call_event = after_call_event + self._perm = perm # Generate default help_text if text was not provided. if 'help_text' not in kwargs: kwargs['help_text'] = ('Saves the command output contents of %s ' @@ -105,6 +105,10 @@ def query(self): return self._query + @property + def perm(self): + return self._perm + def add_to_params(self, parameters, value): value = resolve_given_outfile_path(value) super(QueryOutFileArgument, self).add_to_params(parameters, value) @@ -126,3 +130,4 @@ fp.write('') else: fp.write(contents) + os.chmod(self.value, self.perm) diff -Nru awscli-1.9.9/awscli/customizations/assumerole.py awscli-1.10.1/awscli/customizations/assumerole.py --- awscli-1.9.9/awscli/customizations/assumerole.py 2015-11-24 01:59:32.000000000 +0000 +++ awscli-1.10.1/awscli/customizations/assumerole.py 2016-01-28 22:38:12.000000000 +0000 @@ -1,86 +1,43 @@ import os -import time import json import logging -import getpass - -from dateutil.parser import parse -from datetime import datetime -from dateutil.tz import tzlocal - -from botocore import credentials -from botocore.compat import total_seconds -from botocore.exceptions import PartialCredentialsError +from botocore.exceptions import ProfileNotFound LOG = logging.getLogger(__name__) -class InvalidConfigError(Exception): - pass - - -class RefreshWithMFAUnsupportedError(Exception): - pass - - def register_assume_role_provider(event_handlers): event_handlers.register('session-initialized', - inject_assume_role_provider, - unique_id='inject_assume_role_cred_provider') + inject_assume_role_provider_cache, + unique_id='inject_assume_role_cred_provider_cache') -def inject_assume_role_provider(session, **kwargs): - provider = create_assume_role_provider(session, AssumeRoleProvider) +def inject_assume_role_provider_cache(session, **kwargs): try: - # The final order will be: - # * env - # * assume-role - # * shared-credentials-file - # * ... cred_chain = session.get_component('credential_provider') - cred_chain.insert_before('shared-credentials-file', provider) - except Exception: - # This is ok, it just means that we couldn't create the credential - # provider object. - LOG.debug("Not registering assume-role provider, credential " - "provider from session could not be created.") - - -def create_assume_role_provider(session, provider_cls): - profile_name = session.get_config_variable('profile') or 'default' - load_config = lambda: session.full_config - return provider_cls( - load_config=load_config, - client_creator=session.create_client, - cache=JSONFileCache(AssumeRoleProvider.CACHE_DIR), - profile_name=profile_name, - ) - - -def create_refresher_function(client, params): - def refresh(): - response = client.assume_role(**params) - credentials = response['Credentials'] - # We need to normalize the credential names to - # the values expected by the refresh creds. - return { - 'access_key': credentials['AccessKeyId'], - 'secret_key': credentials['SecretAccessKey'], - 'token': credentials['SessionToken'], - 'expiry_time': credentials['Expiration'], - } - return refresh - - -def create_mfa_serial_refresh(): - def _refresher(): - # We can explore an option in the future to support - # reprompting for MFA, but for now we just error out - # when the temp creds expire. - raise RefreshWithMFAUnsupportedError( - "Cannot refresh credentials: MFA token required.") - return _refresher + except ProfileNotFound: + # If a user has provided a profile that does not exist, + # trying to retrieve components/config on the session + # will raise ProfileNotFound. Sometimes this is invalid: + # + # "ec2 describe-instances --profile unknown" + # + # and sometimes this is perfectly valid: + # + # "configure set region us-west-2 --profile brand-new-profile" + # + # Because we can't know (and don't want to know) whether + # the customer is trying to do something valid, we just + # immediately return. If it's invalid something else + # up the stack will raise ProfileNotFound, otherwise + # the configure (and other) commands will work as expected. + LOG.debug("ProfileNotFound caught when trying to inject " + "assume-role cred provider cache. Not configuring " + "JSONFileCache for assume-role.") + return + provider = cred_chain.get_provider('assume-role') + provider.cache = JSONFileCache() class JSONFileCache(object): @@ -93,7 +50,10 @@ values can be retrieved at a later time. """ - def __init__(self, working_dir): + + CACHE_DIR = os.path.expanduser(os.path.join('~', '.aws', 'cli', 'cache')) + + def __init__(self, working_dir=CACHE_DIR): self._working_dir = working_dir def __contains__(self, cache_key): @@ -125,202 +85,3 @@ def _convert_cache_key(self, cache_key): full_path = os.path.join(self._working_dir, cache_key + '.json') return full_path - - -class AssumeRoleProvider(credentials.CredentialProvider): - - METHOD = 'assume-role' - CACHE_DIR = os.path.expanduser(os.path.join('~', '.aws', 'cli', 'cache')) - ROLE_CONFIG_VAR = 'role_arn' - # Credentials are considered expired (and will be refreshed) once the total - # remaining time left until the credentials expires is less than the - # EXPIRY_WINDOW. - EXPIRY_WINDOW_SECONDS = 60 * 15 - - def __init__(self, load_config, client_creator, cache, profile_name, - prompter=getpass.getpass): - """ - - :type load_config: callable - :param load_config: A function that accepts no arguments, and - when called, will return the full configuration dictionary - for the session (``session.full_config``). - - :type client_creator: callable - :param client_creator: A factory function that will create - a client when called. Has the same interface as - ``botocore.session.Session.create_client``. - - :type cache: JSONFileCache - :param cache: An object that supports ``__getitem__``, - ``__setitem__``, and ``__contains__``. An example - of this is the ``JSONFileCache`` class. - - :type profile_name: str - :param profile_name: The name of the profile. - - :type prompter: callable - :param prompter: A callable that returns input provided - by the user (i.e raw_input, getpass.getpass, etc.). - - """ - self._load_config = load_config - # client_creator is a callable that creates function. - # It's basically session.create_client - self._client_creator = client_creator - self._profile_name = profile_name - self._cache = cache - self._prompter = prompter - # The _loaded_config attribute will be populated from the - # load_config() function once the configuration is actually - # loaded. The reason we go through all this instead of just - # requiring that the loaded_config be passed to us is to that - # we can defer configuration loaded until we actually try - # to load credentials (as opposed to when the object is - # instantiated). - self._loaded_config = {} - - def load(self): - self._loaded_config = self._load_config() - if self._has_assume_role_config_vars(): - return self._load_creds_via_assume_role() - - def _has_assume_role_config_vars(self): - profiles = self._loaded_config.get('profiles', {}) - return self.ROLE_CONFIG_VAR in profiles.get(self._profile_name, {}) - - def _load_creds_via_assume_role(self): - # We can get creds in one of two ways: - # * It can either be cached on disk from an pre-existing session - # * Cache doesn't have the creds (or is expired) so we need to make - # an assume role call to get temporary creds, which we then cache - # for subsequent requests. - creds = self._load_creds_from_cache() - if creds is not None: - LOG.debug("Credentials for role retrieved from cache.") - return creds - else: - # We get the Credential used by botocore as well - # as the original parsed response from the server. - creds, response = self._retrieve_temp_credentials() - cache_key = self._create_cache_key() - self._write_cached_credentials(response, cache_key) - return creds - - def _load_creds_from_cache(self): - cache_key = self._create_cache_key() - try: - from_cache = self._cache[cache_key] - if self._is_expired(from_cache): - # Don't need to delete the cache entry, - # when we refresh via AssumeRole, we'll - # update the cache with the new entry. - LOG.debug("Credentials were found in cache, but they are expired.") - return None - else: - return self._create_creds_from_response(from_cache) - except KeyError: - return None - - def _is_expired(self, credentials): - end_time = parse(credentials['Credentials']['Expiration']) - now = datetime.now(tzlocal()) - seconds = total_seconds(end_time - now) - return seconds < self.EXPIRY_WINDOW_SECONDS - - def _create_cache_key(self): - role_config = self._get_role_config_values() - # On windows, ':' is not allowed in filenames, so we'll - # replace them with '_' instead. - role_arn = role_config['role_arn'].replace(':', '_') - role_session_name=role_config.get('role_session_name') - if role_session_name: - cache_key = '%s--%s--%s' % (self._profile_name, role_arn, role_session_name) - else: - cache_key = '%s--%s' % (self._profile_name, role_arn) - - return cache_key.replace('/', '-') - - def _write_cached_credentials(self, creds, cache_key): - self._cache[cache_key] = creds - - def _get_role_config_values(self): - # This returns the role related configuration. - profiles = self._loaded_config.get('profiles', {}) - try: - source_profile = profiles[self._profile_name]['source_profile'] - role_arn = profiles[self._profile_name]['role_arn'] - mfa_serial = profiles[self._profile_name].get('mfa_serial') - except KeyError as e: - raise PartialCredentialsError(provider=self.METHOD, - cred_var=str(e)) - external_id = profiles[self._profile_name].get('external_id') - role_session_name = profiles[self._profile_name].get('role_session_name') - if source_profile not in profiles: - raise InvalidConfigError( - 'The source_profile "%s" referenced in ' - 'the profile "%s" does not exist.' % ( - source_profile, self._profile_name)) - source_cred_values = profiles[source_profile] - return { - 'role_arn': role_arn, - 'external_id': external_id, - 'source_profile': source_profile, - 'mfa_serial': mfa_serial, - 'source_cred_values': source_cred_values, - 'role_session_name': role_session_name - } - - def _create_creds_from_response(self, response): - config = self._get_role_config_values() - if config.get('mfa_serial') is not None: - # MFA would require getting a new TokenCode which would require - # prompting the user for a new token, so we use a different - # refresh_func. - refresh_func = create_mfa_serial_refresh() - else: - refresh_func = create_refresher_function( - self._create_client_from_config(config), - self._assume_role_base_kwargs(config)) - return credentials.RefreshableCredentials( - access_key=response['Credentials']['AccessKeyId'], - secret_key=response['Credentials']['SecretAccessKey'], - token=response['Credentials']['SessionToken'], - method=self.METHOD, - expiry_time=parse(response['Credentials']['Expiration']), - refresh_using=refresh_func) - - def _create_client_from_config(self, config): - source_cred_values = config['source_cred_values'] - client = self._client_creator( - 'sts', aws_access_key_id=source_cred_values['aws_access_key_id'], - aws_secret_access_key=source_cred_values['aws_secret_access_key'], - aws_session_token=source_cred_values.get('aws_session_token'), - ) - return client - - def _retrieve_temp_credentials(self): - LOG.debug("Retrieving credentials via AssumeRole.") - config = self._get_role_config_values() - client = self._create_client_from_config(config) - - assume_role_kwargs = self._assume_role_base_kwargs(config) - if assume_role_kwargs.get('RoleSessionName') is None: - role_session_name = 'AWS-CLI-session-%s' % (int(time.time())) - assume_role_kwargs['RoleSessionName'] = role_session_name - - response = client.assume_role(**assume_role_kwargs) - creds = self._create_creds_from_response(response) - return creds, response - - def _assume_role_base_kwargs(self, config): - assume_role_kwargs = {'RoleArn': config['role_arn']} - if config['external_id'] is not None: - assume_role_kwargs['ExternalId'] = config['external_id'] - if config['mfa_serial'] is not None: - token_code = self._prompter("Enter MFA code: ") - assume_role_kwargs['SerialNumber'] = config['mfa_serial'] - assume_role_kwargs['TokenCode'] = token_code - if config['role_session_name'] is not None: - assume_role_kwargs['RoleSessionName'] = config['role_session_name'] - return assume_role_kwargs diff -Nru awscli-1.9.9/awscli/customizations/awslambda.py awscli-1.10.1/awscli/customizations/awslambda.py --- awscli-1.9.9/awscli/customizations/awslambda.py 2015-11-24 01:59:32.000000000 +0000 +++ awscli-1.10.1/awscli/customizations/awslambda.py 2016-01-28 22:38:12.000000000 +0000 @@ -23,10 +23,15 @@ "--zip-file must be a file with the fileb:// prefix.\n" "Example usage: --zip-file fileb://path/to/file.zip") +ZIP_DOCSTRING = ('

The path to the zip file of the code you are uploading. ' + 'Example: fileb://code.zip

') + def register_lambda_create_function(cli): cli.register('building-argument-table.lambda.create-function', _extract_code_and_zip_file_arguments) + cli.register('building-argument-table.lambda.update-function-code', + _modify_zipfile_docstring) cli.register('process-cli-arg.lambda.update-function-code', validate_is_zip_file) @@ -38,9 +43,7 @@ def _extract_code_and_zip_file_arguments(session, argument_table, **kwargs): argument_table['zip-file'] = ZipFileArgument( - 'zip-file', help_text=('The path to the zip file of the code you ' - 'are uploading. Example: fileb://code.zip'), - cli_type_name='blob') + 'zip-file', help_text=ZIP_DOCSTRING, cli_type_name='blob') code_argument = argument_table['code'] code_model = copy.deepcopy(code_argument.argument_model) del code_model.members['ZipFile'] @@ -54,6 +57,11 @@ ) +def _modify_zipfile_docstring(session, argument_table, **kwargs): + if 'zip-file' in argument_table: + argument_table['zip-file'].documentation = ZIP_DOCSTRING + + def _should_contain_zip_content(value): if not isinstance(value, bytes): # If it's not bytes it's basically impossible for diff -Nru awscli-1.9.9/awscli/customizations/cloudfront.py awscli-1.10.1/awscli/customizations/cloudfront.py --- awscli-1.9.9/awscli/customizations/cloudfront.py 1970-01-01 00:00:00.000000000 +0000 +++ awscli-1.10.1/awscli/customizations/cloudfront.py 2016-01-28 22:38:12.000000000 +0000 @@ -0,0 +1,259 @@ +# Copyright 2015 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 time +import random + +import rsa +from botocore.utils import parse_to_aware_datetime +from botocore.signers import CloudFrontSigner + +from awscli.arguments import CustomArgument +from awscli.customizations.utils import validate_mutually_exclusive_handler +from awscli.customizations.commands import BasicCommand + + +def register(event_handler): + event_handler.register('building-command-table.cloudfront', _add_sign) + + # Provides a simpler --paths for ``aws cloudfront create-invalidation`` + event_handler.register( + 'building-argument-table.cloudfront.create-invalidation', _add_paths) + event_handler.register( + 'operation-args-parsed.cloudfront.create-invalidation', + validate_mutually_exclusive_handler(['invalidation_batch'], ['paths'])) + + event_handler.register( + 'operation-args-parsed.cloudfront.create-distribution', + validate_mutually_exclusive_handler( + ['default_root_object', 'origin_domain_name'], + ['distribution_config'])) + event_handler.register( + 'building-argument-table.cloudfront.create-distribution', + lambda argument_table, **kwargs: argument_table.__setitem__( + 'origin-domain-name', OriginDomainName(argument_table))) + event_handler.register( + 'building-argument-table.cloudfront.create-distribution', + lambda argument_table, **kwargs: argument_table.__setitem__( + 'default-root-object', CreateDefaultRootObject(argument_table))) + + context = {} + event_handler.register('top-level-args-parsed', context.update) + event_handler.register( + 'operation-args-parsed.cloudfront.update-distribution', + validate_mutually_exclusive_handler( + ['default_root_object'], ['distribution_config'])) + event_handler.register( + 'building-argument-table.cloudfront.update-distribution', + lambda argument_table, **kwargs: argument_table.__setitem__( + 'default-root-object', UpdateDefaultRootObject( + context=context, argument_table=argument_table))) + + +def unique_string(prefix='cli'): + return '%s-%s-%s' % (prefix, int(time.time()), random.randint(1, 1000000)) + + +def _add_paths(argument_table, **kwargs): + argument_table['invalidation-batch'].required = False + argument_table['paths'] = PathsArgument() + + +class PathsArgument(CustomArgument): + + def __init__(self): + doc = ( + 'The space-separated paths to be invalidated.' + ' Note: --invalidation-batch and --paths are mututally exclusive.' + ) + super(PathsArgument, self).__init__('paths', nargs='+', help_text=doc) + + def add_to_params(self, parameters, value): + if value is not None: + parameters['InvalidationBatch'] = { + "CallerReference": unique_string(), + "Paths": {"Quantity": len(value), "Items": value}, + } + + +class ExclusiveArgument(CustomArgument): + DOC = '%s This argument and --%s are mututally exclusive.' + + def __init__(self, name, argument_table, + exclusive_to='distribution-config', help_text=''): + argument_table[exclusive_to].required = False + super(ExclusiveArgument, self).__init__( + name, help_text=self.DOC % (help_text, exclusive_to)) + + def distribution_config_template(self): + return { + "CallerReference": unique_string(), + "Origins": {"Quantity": 0, "Items": []}, + "DefaultCacheBehavior": { + "TargetOriginId": "placeholder", + "ForwardedValues": { + "QueryString": False, + "Cookies": {"Forward": "none"}, + }, + "TrustedSigners": { + "Enabled": False, + "Quantity": 0 + }, + "ViewerProtocolPolicy": "allow-all", + "MinTTL": 0 + }, + "Enabled": True, + "Comment": "", + } + + +class OriginDomainName(ExclusiveArgument): + def __init__(self, argument_table): + super(OriginDomainName, self).__init__( + 'origin-domain-name', argument_table, + help_text='The domain name for your origin.') + + def add_to_params(self, parameters, value): + if value is None: + return + parameters.setdefault( + 'DistributionConfig', self.distribution_config_template()) + origin_id = unique_string(prefix=value) + item = {"Id": origin_id, "DomainName": value, "OriginPath": ''} + if item['DomainName'].endswith('.s3.amazonaws.com'): + # We do not need to detect '.s3[\w-].amazonaws.com' as S3 buckets, + # because CloudFront treats GovCloud S3 buckets as custom domain. + # http://docs.aws.amazon.com/govcloud-us/latest/UserGuide/setting-up-cloudfront.html + item["S3OriginConfig"] = {"OriginAccessIdentity": ""} + else: + item["CustomOriginConfig"] = { + 'HTTPPort': 80, 'HTTPSPort': 443, + 'OriginProtocolPolicy': 'http-only'} + parameters['DistributionConfig']['Origins'] = { + "Quantity": 1, "Items": [item]} + parameters['DistributionConfig']['DefaultCacheBehavior'][ + 'TargetOriginId'] = origin_id + + +class CreateDefaultRootObject(ExclusiveArgument): + def __init__(self, argument_table, help_text=''): + super(CreateDefaultRootObject, self).__init__( + 'default-root-object', argument_table, help_text=help_text or ( + 'The object that you want CloudFront to return (for example, ' + 'index.html) when a viewer request points to your root URL.')) + + def add_to_params(self, parameters, value): + if value is not None: + parameters.setdefault( + 'DistributionConfig', self.distribution_config_template()) + parameters['DistributionConfig']['DefaultRootObject'] = value + + +class UpdateDefaultRootObject(CreateDefaultRootObject): + def __init__(self, context, argument_table): + super(UpdateDefaultRootObject, self).__init__( + argument_table, help_text=( + 'The object that you want CloudFront to return (for example, ' + 'index.html) when a viewer request points to your root URL. ' + 'CLI will automatically make a get-distribution-config call ' + 'to load and preserve your other settings.')) + self.context = context + + def add_to_params(self, parameters, value): + if value is not None: + client = self.context['session'].create_client( + 'cloudfront', + region_name=self.context['parsed_args'].region, + endpoint_url=self.context['parsed_args'].endpoint_url, + verify=self.context['parsed_args'].verify_ssl) + response = client.get_distribution_config(Id=parameters['Id']) + parameters['IfMatch'] = response['ETag'] + parameters['DistributionConfig'] = response['DistributionConfig'] + parameters['DistributionConfig']['DefaultRootObject'] = value + + +def _add_sign(command_table, session, **kwargs): + command_table['sign'] = SignCommand(session) + + +class SignCommand(BasicCommand): + NAME = 'sign' + DESCRIPTION = 'Sign a given url.' + DATE_FORMAT = """Supported formats include: + YYYY-MM-DD (which means 0AM UTC of that day), + YYYY-MM-DDThh:mm:ss (with default timezone as UTC), + YYYY-MM-DDThh:mm:ss+hh:mm or YYYY-MM-DDThh:mm:ss-hh:mm (with offset), + or EpochTime (which always means UTC). + Do NOT use YYYYMMDD, because it will be treated as EpochTime.""" + ARG_TABLE = [ + { + 'name': 'url', + 'no_paramfile': True, # To disable the default paramfile behavior + 'required': True, + 'help_text': 'The URL to be signed', + }, + { + 'name': 'key-pair-id', + 'required': True, + 'help_text': ( + "The active CloudFront key pair Id for the key pair " + "that you're using to generate the signature."), + }, + { + 'name': 'private-key', + 'required': True, + 'help_text': 'file://path/to/your/private-key.pem', + }, + { + 'name': 'date-less-than', 'required': True, + 'help_text': + 'The expiration date and time for the URL. ' + DATE_FORMAT, + }, + { + 'name': 'date-greater-than', + 'help_text': + 'An optional start date and time for the URL. ' + DATE_FORMAT, + }, + { + 'name': 'ip-address', + 'help_text': ( + 'An optional IP address or IP address range to allow client ' + 'making the GET request from. Format: x.x.x.x/x or x.x.x.x'), + }, + ] + + def _run_main(self, args, parsed_globals): + signer = CloudFrontSigner( + args.key_pair_id, RSASigner(args.private_key).sign) + date_less_than = parse_to_aware_datetime(args.date_less_than) + date_greater_than = args.date_greater_than + if date_greater_than is not None: + date_greater_than = parse_to_aware_datetime(date_greater_than) + if date_greater_than is not None or args.ip_address is not None: + policy = signer.build_policy( + args.url, date_less_than, date_greater_than=date_greater_than, + ip_address=args.ip_address) + sys.stdout.write(signer.generate_presigned_url( + args.url, policy=policy)) + else: + sys.stdout.write(signer.generate_presigned_url( + args.url, date_less_than=date_less_than)) + return 0 + + +class RSASigner(object): + def __init__(self, private_key): + self.priv_key = rsa.PrivateKey.load_pkcs1(private_key.encode('utf8')) + + def sign(self, message): + return rsa.sign(message, self.priv_key, 'SHA-1') diff -Nru awscli-1.9.9/awscli/customizations/configure/addmodel.py awscli-1.10.1/awscli/customizations/configure/addmodel.py --- awscli-1.9.9/awscli/customizations/configure/addmodel.py 1970-01-01 00:00:00.000000000 +0000 +++ awscli-1.10.1/awscli/customizations/configure/addmodel.py 2016-01-28 22:38:12.000000000 +0000 @@ -0,0 +1,120 @@ +# Copyright 2015 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 os + +from botocore.model import ServiceModel + +from awscli.customizations.commands import BasicCommand + + +def _get_endpoint_prefix_to_name_mappings(session): + # Get the mappings of endpoint prefixes to service names from the + # available service models. + prefixes_to_services = {} + for service_name in session.get_available_services(): + service_model = session.get_service_model(service_name) + prefixes_to_services[service_model.endpoint_prefix] = service_name + return prefixes_to_services + + +def _get_service_name(session, endpoint_prefix): + if endpoint_prefix in session.get_available_services(): + # Check if the endpoint prefix is a pre-existing service. + # If it is, use that endpoint prefix as the service name. + return endpoint_prefix + else: + # The service may have a different endpoint prefix than its name + # So we need to determine what the correct mapping may be. + + # Figure out the mappings of endpoint prefix to service names. + name_mappings = _get_endpoint_prefix_to_name_mappings(session) + # Determine the service name from the mapping. + # If it does not exist in the mapping, return the original endpoint + # prefix. + return name_mappings.get(endpoint_prefix, endpoint_prefix) + + +def get_model_location(session, service_definition, service_name=None): + """Gets the path of where a service-2.json file should go in ~/.aws/models + + :type session: botocore.session.Session + :param session: A session object + + :type service_definition: dict + :param service_definition: The json loaded service definition + + :type service_name: str + :param service_name: The service name to use. If this not provided, + this will be determined from a combination of available services + and the service definition. + + :returns: The path to where are model should be placed based on + the service defintion and the current services in botocore. + """ + # Add the ServiceModel abstraction over the service json definition to + # make it easier to work with. + service_model = ServiceModel(service_definition) + + # Determine the service_name if not provided + if service_name is None: + endpoint_prefix = service_model.endpoint_prefix + service_name = _get_service_name(session, endpoint_prefix) + api_version = service_model.api_version + + # For the model location we only want the custom data path (~/.aws/models + # not the one set by AWS_DATA_PATH) + data_path = session.get_component('data_loader').CUSTOMER_DATA_PATH + # Use the version of the model to determine the file's naming convention. + service_model_name = ( + 'service-%d.json' % int( + float(service_definition.get('version', '2.0')))) + return os.path.join(data_path, service_name, api_version, + service_model_name) + + +class AddModelCommand(BasicCommand): + NAME = 'add-model' + DESCRITPION = ( + 'Adds a service JSON model to the appropriate location in ' + '~/.aws/models. Once the model gets added, CLI commands and Boto3 ' + 'clients will be immediately available for the service JSON model ' + 'provided.' + ) + ARG_TABLE = [ + {'name': 'service-model', 'required': True, 'help_text': ( + 'The contents of the service JSON model.')}, + {'name': 'service-name', 'help_text': ( + 'Overrides the default name used by the service JSON ' + 'model to generate CLI service commands and Boto3 clients.')} + ] + + def _run_main(self, parsed_args, parsed_globals): + service_definition = json.loads(parsed_args.service_model) + + # Get the path to where the model should be written + model_location = get_model_location( + self._session, service_definition, parsed_args.service_name + ) + + # If the service_name/api_version directories do not exist, + # then create them. + model_directory = os.path.dirname(model_location) + if not os.path.exists(model_directory): + os.makedirs(model_directory) + + # Write the model to the specified location + with open(model_location, 'w') as f: + f.write(parsed_args.service_model) + + return 0 diff -Nru awscli-1.9.9/awscli/customizations/configure/__init__.py awscli-1.10.1/awscli/customizations/configure/__init__.py --- awscli-1.9.9/awscli/customizations/configure/__init__.py 1970-01-01 00:00:00.000000000 +0000 +++ awscli-1.10.1/awscli/customizations/configure/__init__.py 2016-01-28 22:38:12.000000000 +0000 @@ -0,0 +1,602 @@ +# 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 + +from botocore.exceptions import ProfileNotFound + +from awscli.compat import raw_input +from awscli.customizations.commands import BasicCommand +from awscli.customizations.configure.addmodel import AddModelCommand + + +logger = logging.getLogger(__name__) +NOT_SET = '' + +PREDEFINED_SECTION_NAMES = ('preview', 'plugins') + +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