FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Expose waiters in the CLI · ProGamerCode/aws-cli@b0d2d49 · GitHub

forked from aws/aws-cli

Commit b0d2d49

Browse files
committed
Expose waiters in the CLI
Add a ``wait`` command to all services that have waiters. For each type of waiter, a subcommand representing that waiter was added. For example, to wait for an ec2 instance to reach the running state, the wait command would be specified as ``aws ec2 wait instance-running``.
1 parent 0a5605e commit b0d2d49

8 files changed

Lines changed: 608 additions & 6 deletions

File tree

‎awscli/clidocs.py‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,16 @@ def doc_subitems_start(self, help_command, **kwargs):
232232

233233
def doc_subitem(self, command_name, help_command, **kwargs):
234234
doc = help_command.doc
235-
doc.style.tocitem(command_name)
235+
subcommand = help_command.command_table[command_name]
236+
subcommand_table = getattr(subcommand, 'subcommand_table', {})
237+
# If the subcommand table has commands in it,
238+
# direct the subitem to the command's index because
239+
# it has more subcommands to be documented.
240+
if (len(subcommand_table) > 0):
241+
file_name = '%s/index' % command_name
242+
doc.style.tocitem(command_name, file_name=file_name)
243+
else:
244+
doc.style.tocitem(command_name)
236245

237246

238247
class OperationDocumentEventHandler(CLIDocumentEventHandler):

‎awscli/clidriver.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,8 @@ def _create_command_table(self):
366366
service_object=service_object)
367367
self.session.emit('building-command-table.%s' % self._name,
368368
command_table=command_table,
369-
session=self.session)
369+
session=self.session,
370+
service_object=service_object)
370371
return command_table
371372

372373
def create_help_command(self):

‎awscli/customizations/waiters.py‎

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
# Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"). You
4+
# may not use this file except in compliance with the License. A copy of
5+
# the License is located at
6+
#
7+
# http://aws.amazon.com/apache2.0/
8+
#
9+
# or in the "license" file accompanying this file. This file is
10+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11+
# ANY KIND, either express or implied. See the License for the specific
12+
# language governing permissions and limitations under the License.
13+
from botocore import xform_name
14+
15+
from awscli.clidriver import ServiceOperation
16+
from awscli.customizations.commands import BasicCommand, BasicHelp, \
17+
BasicDocHandler
18+
19+
20+
def register_add_waiters(cli):
21+
cli.register('building-command-table', add_waiters)
22+
23+
24+
def add_waiters(command_table, session, service_object=None, **kwargs):
25+
# If a service object was passed in, try to add a wait command.
26+
if service_object is not None:
27+
# Get a client out of the service object.
28+
client = translate_service_object_to_client(service_object)
29+
# Find all of the waiters for that client.
30+
waiters = client.waiter_names
31+
# If there are waiters make a wait command.
32+
if waiters:
33+
command_table['wait'] = WaitCommand(client, service_object)
34+
35+
36+
def translate_service_object_to_client(service_object):
37+
# Create a client from a service object.
38+
session = service_object.session
39+
return session.create_client(service_object.service_name)
40+
41+
42+
class WaitCommand(BasicCommand):
43+
NAME = 'wait'
44+
DESCRIPTION = 'Wait until a particular condition is satisfied.'
45+
46+
def __init__(self, client, service_object):
47+
self._client = client
48+
self._service_object = service_object
49+
self.waiter_cmd_builder = WaiterStateCommandBuilder(
50+
client=self._client,
51+
service_object=self._service_object
52+
)
53+
super(WaitCommand, self).__init__(self._service_object.session)
54+
55+
def _run_main(self, parsed_args, parsed_globals):
56+
if parsed_args.subcommand is None:
57+
raise ValueError("usage: aws [options] <command> <subcommand> "
58+
"[parameters]\naws: error: too few arguments")
59+
60+
def _build_subcommand_table(self):
61+
subcommand_table = super(WaitCommand, self)._build_subcommand_table()
62+
self.waiter_cmd_builder.build_all_waiter_state_cmds(subcommand_table)
63+
return subcommand_table
64+
65+
def create_help_command(self):
66+
return BasicHelp(self._session, self,
67+
command_table=self.subcommand_table,
68+
arg_table=self.arg_table,
69+
event_handler_class=WaiterCommandDocHandler)
70+
71+
72+
class WaiterStateCommandBuilder(object):
73+
def __init__(self, client, service_object):
74+
self._client = client
75+
self._service_object = service_object
76+
77+
def build_all_waiter_state_cmds(self, subcommand_table):
78+
"""This adds waiter state commands to the subcommand table passed in.
79+
80+
This is the method that adds waiter state commands like
81+
``instance-running`` to ``ec2 wait``.
82+
"""
83+
waiters = self._client.waiter_names
84+
for waiter_name in waiters:
85+
waiter_cli_name = waiter_name.replace('_', '-')
86+
subcommand_table[waiter_cli_name] = \
87+
self._build_waiter_state_cmd(waiter_name)
88+
89+
def _build_waiter_state_cmd(self, waiter_name):
90+
# Get the waiter
91+
waiter = self._client.get_waiter(waiter_name)
92+
93+
# Create the cli name for the waiter operation
94+
waiter_cli_name = waiter_name.replace('_', '-')
95+
96+
# Obtain the name of the service operation that is used to implement
97+
# the specified waiter.
98+
operation_name = waiter.config.operation
99+
100+
# Create an operation object to make a command for the waiter. The
101+
# operation object is used to generate the arguments for the waiter
102+
# state command.
103+
operation_object = self._service_object.get_operation(operation_name)
104+
waiter_state_command = WaiterStateCommand(
105+
name=waiter_cli_name, parent_name='wait',
106+
operation_object=operation_object,
107+
operation_caller=WaiterCaller(self._client, waiter),
108+
service_object=self._service_object
109+
)
110+
# Build the top level description for the waiter state command.
111+
# Most waiters do not have a description so they need to be generated
112+
# using the waiter configuration.
113+
waiter_state_doc_builder = WaiterStateDocBuilder(waiter.config)
114+
description = waiter_state_doc_builder.build_waiter_state_description()
115+
waiter_state_command.DESCRIPTION = description
116+
return waiter_state_command
117+
118+
119+
class WaiterStateDocBuilder(object):
120+
SUCCESS_DESCRIPTIONS = {
121+
'error': u'%s is thrown ',
122+
'path': u'%s ',
123+
'pathAll': u'%s for all elements ',
124+
'pathAny': u'%s for any element ',
125+
'status': u'%s response is received '
126+
}
127+
128+
def __init__(self, waiter_config):
129+
self._waiter_config = waiter_config
130+
131+
def build_waiter_state_description(self):
132+
description = self._waiter_config.description
133+
# Use the description provided in the waiter config file. If no
134+
# description is provided, use a heuristic to generate a description
135+
# for the waiter.
136+
if not description:
137+
description = u'Wait until '
138+
# Look at all of the acceptors and find the success state
139+
# acceptor.
140+
for acceptor in self._waiter_config.acceptors:
141+
# Build the description off of the success acceptor.
142+
if acceptor.state == 'success':
143+
description += self._build_success_description(acceptor)
144+
break
145+
# Include what operation is being used.
146+
description += self._build_operation_description(
147+
self._waiter_config.operation)
148+
return description
149+
150+
def _build_success_description(self, acceptor):
151+
matcher = acceptor.matcher
152+
# Pick the description template to use based on what the matcher is.
153+
success_description = self.SUCCESS_DESCRIPTIONS[matcher]
154+
resource_description = None
155+
# If success is based off of the state of a resource include the
156+
# description about what resource is looked at.
157+
if matcher in ['path', 'pathAny', 'pathAll']:
158+
resource_description = u'JMESPath query %s returns ' % \
159+
acceptor.argument
160+
# Prepend the resource description to the template description
161+
success_description = resource_description + success_description
162+
# Complete the description by filling in the expected success state.
163+
full_success_description = success_description % acceptor.expected
164+
return full_success_description
165+
166+
def _build_operation_description(self, operation):
167+
operation_name = xform_name(operation).replace('_', '-')
168+
return u'when polling with ``%s``.' % operation_name
169+
170+
171+
class WaiterCaller(object):
172+
def __init__(self, client, waiter):
173+
self._client = client
174+
self._waiter = waiter
175+
176+
def invoke(self, operation_object, parameters, parsed_globals):
177+
# Create the endpoint based on the parsed globals
178+
endpoint = operation_object.service.get_endpoint(
179+
region_name=parsed_globals.region,
180+
endpoint_url=parsed_globals.endpoint_url,
181+
verify=parsed_globals.verify_ssl)
182+
# Change the client's endpoint using the newly configured endpoint
183+
self._client._endpoint = endpoint
184+
# Call the waiter's wait method.
185+
self._waiter.wait(**parameters)
186+
return 0
187+
188+
189+
class WaiterStateCommand(ServiceOperation):
190+
DESCRIPTION = ''
191+
192+
def create_help_command(self):
193+
help_command = super(WaiterStateCommand, self).create_help_command()
194+
# Change the operation object's description by changing it to the
195+
# description for a waiter state command.
196+
self._operation_object.documentation = self.DESCRIPTION
197+
# Change the output shape because waiters provide no output.
198+
self._operation_object.model.output_shape = None
199+
return help_command
200+
201+
202+
class WaiterCommandDocHandler(BasicDocHandler):
203+
def doc_synopsis_start(self, help_command, **kwargs):
204+
pass
205+
206+
def doc_synopsis_option(self, arg_name, help_command, **kwargs):
207+
pass
208+
209+
def doc_synopsis_end(self, help_command, **kwargs):
210+
pass
211+
212+
def doc_options_start(self, help_command, **kwargs):
213+
pass
214+
215+
def doc_option(self, arg_name, help_command, **kwargs):
216+
pass

‎awscli/handlers.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
from awscli.customizations.cliinputjson import register_cli_input_json
5252
from awscli.customizations.generatecliskeleton import \
5353
register_generate_cli_skeleton
54+
from awscli.customizations.waiters import register_add_waiters
5455

5556

5657
def awscli_initialize(event_handlers):
@@ -105,3 +106,4 @@ def awscli_initialize(event_handlers):
105106
register_cloudsearchdomain(event_handlers)
106107
register_s3_endpoint(event_handlers)
107108
register_generate_cli_skeleton(event_handlers)
109+
register_add_waiters(event_handlers)

‎doc/source/htmlgen‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,10 @@ def do_operation(driver, service_path, operation_name, operation_command):
3333
help_command(None, None)
3434

3535

36-
def do_service(driver, ref_path, service_name, service_command):
37-
print('...%s' % service_name)
36+
def do_service(driver, ref_path, service_name, service_command,
37+
is_top_level_service=True):
38+
if is_top_level_service:
39+
print('...%s' % service_name)
3840
service_path = os.path.join(ref_path, service_name)
3941
if not os.path.isdir(service_path):
4042
os.mkdir(service_path)
@@ -50,7 +52,16 @@ def do_service(driver, ref_path, service_name, service_command):
5052
if operation_name == 'help':
5153
continue
5254
operation_command = help_command.command_table[operation_name]
53-
do_operation(driver, service_path, operation_name, operation_command)
55+
subcommand_table = getattr(operation_command, 'subcommand_table', {})
56+
# If the operation command has a subcommand table with commands
57+
# in it, treat it as a service command as opposed to an operation
58+
# command.
59+
if (len(subcommand_table) > 0):
60+
do_service(driver, service_path, operation_name,
61+
operation_command, False)
62+
else:
63+
do_operation(driver, service_path, operation_name,
64+
operation_command)
5465

5566

5667
def do_provider(driver):
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"). You
4+
# may not use this file except in compliance with the License. A copy of
5+
# the License is located at
6+
#
7+
# http://aws.amazon.com/apache2.0/
8+
#
9+
# or in the "license" file accompanying this file. This file is
10+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11+
# ANY KIND, either express or implied. See the License for the specific
12+
# language governing permissions and limitations under the License.
13+
import botocore.session
14+
import random
15+
16+
from awscli.testutils import unittest, aws
17+
18+
19+
class TestDynamoDBWait(unittest.TestCase):
20+
def setUp(self):
21+
self.session = botocore.session.get_session()
22+
self.client = self.session.create_client('dynamodb', 'us-west-2')
23+
24+
def test_wait_table_exists(self):
25+
# Create a table.
26+
table_name = 'awscliddb-%s' % random.randint(1, 10000)
27+
self.client.create_table(
28+
TableName=table_name,
29+
ProvisionedThroughput={"ReadCapacityUnits": 5,
30+
"WriteCapacityUnits": 5},
31+
KeySchema=[{"AttributeName": "foo", "KeyType": "HASH"}],
32+
AttributeDefinitions=[{"AttributeName": "foo",
33+
"AttributeType": "S"}])
34+
self.addCleanup(self.client.delete_table, TableName=table_name)
35+
36+
# Wait for the table to be active.
37+
p = aws(
38+
'dynamodb wait table-exists --table-name %s --region us-west-2' %
39+
table_name)
40+
self.assertEqual(p.rc, 0)
41+
42+
# Make sure the table is active.
43+
parsed = self.client.describe_table(TableName=table_name)
44+
self.assertEqual(parsed['Table']['TableStatus'], 'ACTIVE')

0 commit comments

Comments
 (0)

Back | FazBrowse Home | New Git URL