Skip to content

Commit

Permalink
Merge branch 'release-1.11.23'
Browse files Browse the repository at this point in the history
* release-1.11.23:
  Bumping version to 1.11.23
  Update changelog based on model updates
  Add todo comment for refactoring
  Fix test for python 3
  Add changelog entry
  Add test for list with string elements
  Cleaned up formatting and comments
  Add timestamp support in --generate-cli-skeleton
  Add --generate-cli-skeleton output
  • Loading branch information
AWS committed Nov 30, 2016
2 parents 939d49d + 678c4f4 commit 585dedc
Show file tree
Hide file tree
Showing 10 changed files with 289 additions and 39 deletions.
27 changes: 27 additions & 0 deletions .changes/1.11.23.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[
{
"category": "``polly``",
"description": "Update polly command to latest version",
"type": "feature"
},
{
"category": "``snowball``",
"description": "Update snowball command to latest version",
"type": "feature"
},
{
"category": "``rekognition``",
"description": "Update rekognition command to latest version",
"type": "feature"
},
{
"category": "``lightsail``",
"description": "Update lightsail command to latest version",
"type": "feature"
},
{
"category": "``--generate-cli-skeleton output``",
"description": "Add support for generating sample output for command",
"type": "feature"
}
]
10 changes: 10 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@
CHANGELOG
=========

1.11.23
=======

* feature:``polly``: Update polly command to latest version
* feature:``snowball``: Update snowball command to latest version
* feature:``rekognition``: Update rekognition command to latest version
* feature:``lightsail``: Update lightsail command to latest version
* feature:``--generate-cli-skeleton output``: Add support for generating sample output for command


1.11.22
=======

Expand Down
2 changes: 1 addition & 1 deletion awscli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"""
import os

__version__ = '1.11.22'
__version__ = '1.11.23'

#
# Get our data path to be added to botocore's search path
Expand Down
10 changes: 8 additions & 2 deletions awscli/clidriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -666,15 +666,21 @@ def invoke(self, service_name, operation_name, parameters, parsed_globals):
service_name, region_name=parsed_globals.region,
endpoint_url=parsed_globals.endpoint_url,
verify=parsed_globals.verify_ssl)
response = self._make_client_call(
client, operation_name, parameters, parsed_globals)
self._display_response(operation_name, response, parsed_globals)
return 0

def _make_client_call(self, client, operation_name, parameters,
parsed_globals):
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)
response = paginator.paginate(**parameters)
else:
response = getattr(client, xform_name(operation_name))(
**parameters)
self._display_response(operation_name, response, parsed_globals)
return 0
return response

def _display_response(self, command_name, response,
parsed_globals):
Expand Down
108 changes: 78 additions & 30 deletions awscli/customizations/generatecliskeleton.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@
import json
import sys

from botocore import xform_name
from botocore.stub import Stubber
from botocore.utils import ArgumentGenerator

from awscli.clidriver import CLIOperationCaller
from awscli.customizations.arguments import OverrideRequiredArgsArgument
from awscli.utils import json_encoder


def register_generate_cli_skeleton(cli):
Expand All @@ -36,18 +40,21 @@ class GenerateCliSkeletonArgument(OverrideRequiredArgsArgument):
The argument, if present in the command line, will prevent the intended
command from taking place. Instead, it will generate a JSON skeleton and
print it to standard output. This JSON skeleton then can be filled out
and can be used as input to ``--input-cli-json`` in order to run the
command with the filled out JSON skeleton.
print it to standard output.
"""
ARG_DATA = {
'name': 'generate-cli-skeleton',
'help_text': 'Prints a sample input JSON to standard output. Note the '
'specified operation is not run if this argument is '
'specified. The sample input can be used as an argument '
'for ``--cli-input-json``.',
'action': 'store_true',
'group_name': 'generate_cli_skeleton'
'help_text': (
'Prints a JSON skeleton to standard output without sending '
'an API request. If provided with no value or the value '
'``input``, prints a sample input JSON that can be used as an '
'argument for ``--cli-input-json``. If provided with the value '
'``output``, it validates the command inputs and returns a '
'sample output JSON for that command.'
),
'nargs': '?',
'const': 'input',
'choices': ['input', 'output'],
}

def __init__(self, session, operation_model):
Expand All @@ -59,29 +66,70 @@ def _register_argument_action(self):
'calling-command.*', self.generate_json_skeleton)
super(GenerateCliSkeletonArgument, self)._register_argument_action()

def override_required_args(self, argument_table, args, **kwargs):
arg_name = '--' + self.name
if arg_name in args:
arg_location = args.index(arg_name)
try:
# If the value of --generate-cli-skeleton is ``output``,
# do not force required arguments to be optional as
# ``--generate-cli-skeleton output`` validates commands
# as well as print out the sample output.
if args[arg_location + 1] == 'output':
return
except IndexError:
pass
super(GenerateCliSkeletonArgument, self).override_required_args(
argument_table, args, **kwargs)

def generate_json_skeleton(self, call_parameters, parsed_args,
parsed_globals, **kwargs):

# Only perform the method if the ``--generate-cli-skeleton`` was
# included in the command line.
if getattr(parsed_args, 'generate_cli_skeleton', False):

# Obtain the model of the operation
if getattr(parsed_args, 'generate_cli_skeleton', None):
for_output = parsed_args.generate_cli_skeleton == 'output'
operation_model = self._operation_model

# Generate the skeleton based on the ``input_shape``.
argument_generator = ArgumentGenerator()
operation_input_shape = operation_model.input_shape
# If the ``input_shape`` is ``None``, generate an empty
# dictionary.
if operation_input_shape is None:
skeleton = {}
if for_output:
service_name = operation_model.service_model.service_name
operation_name = operation_model.name
# TODO: It would be better to abstract this logic into
# classes for both the input and output option such that
# a similar set of inputs are taken in and output
# similar functionality.
return StubbedCLIOperationCaller(self._session).invoke(
service_name, operation_name, call_parameters,
parsed_globals)
else:
skeleton = argument_generator.generate_skeleton(
operation_input_shape)

# Write the generated skeleton to standard output.
sys.stdout.write(json.dumps(skeleton, indent=4))
sys.stdout.write('\n')
# This is the return code
return 0
argument_generator = ArgumentGenerator()
operation_input_shape = operation_model.input_shape
if operation_input_shape is None:
skeleton = {}
else:
skeleton = argument_generator.generate_skeleton(
operation_input_shape)

sys.stdout.write(
json.dumps(skeleton, indent=4, default=json_encoder)
)
sys.stdout.write('\n')
return 0


class StubbedCLIOperationCaller(CLIOperationCaller):
"""A stubbed CLIOperationCaller
It generates a fake response and uses the response and provided parameters
to make a stubbed client call for an operation command.
"""
def _make_client_call(self, client, operation_name, parameters,
parsed_globals):
method_name = xform_name(operation_name)
operation_model = client.meta.service_model.operation_model(
operation_name)
fake_response = {}
if operation_model.output_shape:
argument_generator = ArgumentGenerator(use_member_names=True)
fake_response = argument_generator.generate_skeleton(
operation_model.output_shape)
with Stubber(client) as stubber:
stubber.add_response(method_name, fake_response)
return getattr(client, method_name)(**parameters)
2 changes: 1 addition & 1 deletion doc/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
# The short X.Y version.
version = '1.11.'
# The full version, including alpha/beta/rc tags.
release = '1.11.22'
release = '1.11.23'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ universal = 1

[metadata]
requires-dist =
botocore==1.4.79
botocore==1.4.80
colorama>=0.2.5,<=0.3.7
docutils>=0.10
rsa>=3.1.2,<=3.5.0
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import awscli


requires = ['botocore==1.4.79',
requires = ['botocore==1.4.80',
'colorama>=0.2.5,<=0.3.7',
'docutils>=0.10',
'rsa>=3.1.2,<=3.5.0',
Expand Down
103 changes: 103 additions & 0 deletions tests/functional/test_generatecliskeleton.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Copyright 2016 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

from awscli.testutils import BaseAWSCommandParamsTest


class TestGenerateCliSkeletonOutput(BaseAWSCommandParamsTest):
def test_generate_cli_skeleton_output(self):
cmdline = 'ec2 describe-regions --generate-cli-skeleton output'
stdout, _, _ = self.run_cmd(cmdline)
# The format of the response should be a json blob with the
# following structure:
# {
# "Regions": [
# {
# "RegionName": "RegionName",
# "Endpoint": "Endpoint"
# }
# ]
# }
#
# We assert only components of the response in case members
# are added in the future that would break an exactly equals
# assertion
skeleton_output = json.loads(stdout)
self.assertIn('Regions', skeleton_output)
self.assertEqual(
skeleton_output['Regions'][0]['RegionName'], 'RegionName')
self.assertEqual(
skeleton_output['Regions'][0]['Endpoint'], 'Endpoint')

def test_can_pass_in_input_parameters(self):
cmdline = 'ec2 describe-regions --generate-cli-skeleton output '
cmdline += ' --region-names us-east-1'
stdout, _, _ = self.assert_params_for_cmd(
cmdline, {'RegionNames': ['us-east-1']})

# Make sure the output has the proper mocked response as well.
skeleton_output = json.loads(stdout)
self.assertIn('Regions', skeleton_output)
self.assertEqual(
skeleton_output['Regions'][0]['RegionName'], 'RegionName')
self.assertEqual(
skeleton_output['Regions'][0]['Endpoint'], 'Endpoint')

def test_when_no_output_shape(self):
cmdline = 'ec2 attach-internet-gateway '
cmdline += '--internet-gateway-id igw-c0a643a9 --vpc-id vpc-a01106 '
cmdline += '--generate-cli-skeleton output'
stdout, _, _ = self.assert_params_for_cmd(
cmdline,
{'InternetGatewayId': 'igw-c0a643a9', 'VpcId': 'vpc-a01106'})
# There should be no output as the command has no output shape
self.assertEqual('', stdout)

def test_can_handle_timestamps(self):
cmdline = 's3api list-buckets --generate-cli-skeleton output'
stdout, _, _ = self.run_cmd(cmdline)
skeleton_output = json.loads(stdout)
# The CreationDate has the type of timestamp
self.assertEqual(
skeleton_output['Buckets'][0]['CreationDate'],
'1970-01-01T00:00:00'
)

def test_can_handle_lists_with_strings_that_have_a_min_length(self):
cmdline = 'dynamodb list-tables --generate-cli-skeleton output'
stdout, _, _ = self.run_cmd(cmdline)
skeleton_output = json.loads(stdout)
self.assertEqual(skeleton_output['TableNames'], ['TableName'])

def test_respects_formatting(self):
cmdline = 'ec2 describe-regions --generate-cli-skeleton output '
cmdline += ' --query Regions[].RegionName --output text'
stdout, _, _ = self.run_cmd(cmdline)
self.assertEqual(stdout, 'RegionName\n')

def test_validates_at_command_line_level(self):
cmdline = 'ec2 create-vpc --generate-cli-skeleton output'
stdout, stderr, _ = self.run_cmd(cmdline, expected_rc=2)
self.assertIn('required', stderr)
self.assertIn('--cidr-block', stderr)
self.assertEqual('', stdout)

def test_validates_at_client_level(self):
cmdline = 'ec2 describe-instances --generate-cli-skeleton output '
# Note: The for --filters instead of Value the key should be Values
# which should throw a validation error.
cmdline += '--filters Name=instance-id,Value=foo'
stdout, stderr, _ = self.run_cmd(cmdline, expected_rc=255)
self.assertIn('Unknown parameter in Filters[0]', stderr)
self.assertEqual('', stdout)

0 comments on commit 585dedc

Please sign in to comment.