| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 0a5605e commit b0d2d49
8 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -232,7 +232,16 @@ def doc_subitems_start(self, help_command, **kwargs): | |||
| 232 | 232 | ||
| 233 | 233 | def doc_subitem(self, command_name, help_command, **kwargs): | |
| 234 | 234 | 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) | ||
| 236 | 245 | ||
| 237 | 246 | ||
| 238 | 247 | class OperationDocumentEventHandler(CLIDocumentEventHandler): | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -366,7 +366,8 @@ def _create_command_table(self): | |||
| 366 | 366 | service_object=service_object) | |
| 367 | 367 | self.session.emit('building-command-table.%s' % self._name, | |
| 368 | 368 | command_table=command_table, | |
| 369 | - session=self.session) | ||
| 369 | + session=self.session, | ||
| 370 | + service_object=service_object) | ||
| 370 | 371 | return command_table | |
| 371 | 372 | ||
| 372 | 373 | def create_help_command(self): | |
| Original file line number | Diff line number | Diff 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 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -51,6 +51,7 @@ | |||
| 51 | 51 | from awscli.customizations.cliinputjson import register_cli_input_json | |
| 52 | 52 | from awscli.customizations.generatecliskeleton import \ | |
| 53 | 53 | register_generate_cli_skeleton | |
| 54 | + from awscli.customizations.waiters import register_add_waiters | ||
| 54 | 55 | ||
| 55 | 56 | ||
| 56 | 57 | def awscli_initialize(event_handlers): | |
@@ -105,3 +106,4 @@ def awscli_initialize(event_handlers): | |||
| 105 | 106 | register_cloudsearchdomain(event_handlers) | |
| 106 | 107 | register_s3_endpoint(event_handlers) | |
| 107 | 108 | register_generate_cli_skeleton(event_handlers) | |
| 109 | + register_add_waiters(event_handlers) | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -33,8 +33,10 @@ def do_operation(driver, service_path, operation_name, operation_command): | |||
| 33 | 33 | help_command(None, None) | |
| 34 | 34 | ||
| 35 | 35 | ||
| 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) | ||
| 38 | 40 | service_path = os.path.join(ref_path, service_name) | |
| 39 | 41 | if not os.path.isdir(service_path): | |
| 40 | 42 | os.mkdir(service_path) | |
@@ -50,7 +52,16 @@ def do_service(driver, ref_path, service_name, service_command): | |||
| 50 | 52 | if operation_name == 'help': | |
| 51 | 53 | continue | |
| 52 | 54 | 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) | ||
| 54 | 65 | ||
| 55 | 66 | ||
| 56 | 67 | def do_provider(driver): | |
| Original file line number | Diff line number | Diff 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') | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments