Skip to content

Commit 1fb16b8

Browse files
Refactor of command line argument parsing to single file (#278)
Simplifies command line parsing and ensures naming consistency between samples and the other AWS IoT SDKs. * Simplified command line parsing * Fixed tests not working correctly after simplified command line parsing * Added action support to command line parser * Added additional command groups
1 parent b3b1d99 commit 1fb16b8

File tree

10 files changed

+192
-169
lines changed

10 files changed

+192
-169
lines changed

codebuild/samples/pubsub-linux.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@ echo "Mqtt Direct test"
1212
python3 pubsub.py --endpoint $ENDPOINT --key /tmp/privatekey.pem --cert /tmp/certificate.pem
1313

1414
echo "Websocket test"
15-
python3 pubsub.py --endpoint $ENDPOINT --use-websocket --signing-region us-east-1
15+
python3 pubsub.py --endpoint $ENDPOINT --use_websocket --signing_region us-east-1
1616

1717
popd

samples/README.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Source: `samples/pubsub.py`
2323

2424
Run the sample like this:
2525
``` sh
26-
python3 pubsub.py --endpoint <endpoint> --root-ca <file> --cert <file> --key <file>
26+
python3 pubsub.py --endpoint <endpoint> --ca_file <file> --cert <file> --key <file>
2727
```
2828

2929
Your Thing's
@@ -120,7 +120,7 @@ To run this sample using [SoftHSM2](https://www.opendnssec.org/softhsm/) as the
120120
121121
5) Now you can run the sample:
122122
```sh
123-
python3 pkcs11_pubsub.py --endpoint <xxxx-ats.iot.xxxx.amazonaws.com> --root-ca <AmazonRootCA1.pem> --cert <certificate.pem.crt> --pkcs11-lib <libsofthsm2.so> --pin <user-pin> --token-label <token-label> --key-label <key-label>
123+
python3 pkcs11_pubsub.py --endpoint <xxxx-ats.iot.xxxx.amazonaws.com> --ca_file <AmazonRootCA1.pem> --cert <certificate.pem.crt> --pkcs11_lib <libsofthsm2.so> --pin <user-pin> --token_label <token-label> --key_label <key-label>
124124
125125
126126
## Windows Certificate PubSub
@@ -210,7 +210,7 @@ Source: `samples/shadow.py`
210210
211211
Run the sample like this:
212212
``` sh
213-
python3 shadow.py --endpoint <endpoint> --root-ca <file> --cert <file> --key <file> --thing-name <name>
213+
python3 shadow.py --endpoint <endpoint> --ca_file <file> --cert <file> --key <file> --thing-name <name>
214214
```
215215
216216
Your Thing's
@@ -296,7 +296,7 @@ Source: `samples/jobs.py`
296296
297297
Run the sample like this:
298298
``` sh
299-
python3 jobs.py --endpoint <endpoint> --root-ca <file> --cert <file> --key <file> --thing-name <name>
299+
python3 jobs.py --endpoint <endpoint> --ca_file <file> --cert <file> --key <file> --thing_name <name>
300300
```
301301
302302
Your Thing's
@@ -368,12 +368,12 @@ Source: `samples/fleetprovisioning.py`
368368

369369
Run the sample using createKeysAndCertificate:
370370
``` sh
371-
python3 fleetprovisioning.py --endpoint <endpoint> --root-ca <file> --cert <file> --key <file> --templateName <name> --templateParameters <parameters>
371+
python3 fleetprovisioning.py --endpoint <endpoint> --ca_file <file> --cert <file> --key <file> --template_name <name> --template_parameters <parameters>
372372
```
373373

374374
Run the sample using createCertificateFromCsr:
375375
``` sh
376-
python3 fleetprovisioning.py --endpoint <endpoint> --root-ca <file> --cert <file> --key <file> --templateName <name> --templateParameters <parameters> --csr <csr file>
376+
python3 fleetprovisioning.py --endpoint <endpoint> --ca_file <file> --cert <file> --key <file> --template_name <name> --template_parameters <parameters> --csr <csr file>
377377
```
378378

379379
Your Thing's
@@ -489,11 +489,11 @@ and `--key` appropriately:
489489
``` sh
490490
python3 fleetprovisioning.py \
491491
--endpoint [your endpoint]-ats.iot.[region].amazonaws.com \
492-
--root-ca [pathToRootCA] \
492+
--ca_file [pathToRootCA] \
493493
--cert /tmp/provision.cert.pem \
494494
--key /tmp/provision.private.key \
495-
--templateName [TemplateName] \
496-
--templateParameters "{\"SerialNumber\":\"1\",\"DeviceLocation\":\"Seattle\"}"
495+
--template_name [TemplateName] \
496+
--template_parameters "{\"SerialNumber\":\"1\",\"DeviceLocation\":\"Seattle\"}"
497497
```
498498

499499
Notice that we provided substitution values for the two parameters in the template body, `DeviceLocation` and `SerialNumber`.
@@ -529,11 +529,11 @@ using a permanent certificate set, replace the paths specified in the `--cert` a
529529
``` sh
530530
python3 fleetprovisioning.py \
531531
--endpoint [your endpoint]-ats.iot.[region].amazonaws.com \
532-
--root-ca [pathToRootCA] \
532+
--ca_file [pathToRootCA] \
533533
--cert /tmp/provision.cert.pem \
534534
--key /tmp/provision.private.key \
535-
--templateName [TemplateName] \
536-
--templateParameters "{\"SerialNumber\":\"1\",\"DeviceLocation\":\"Seattle\"}" \
535+
--template_name [TemplateName] \
536+
--template_parameters "{\"SerialNumber\":\"1\",\"DeviceLocation\":\"Seattle\"}" \
537537
--csr /tmp/deviceCert.csr
538538
```
539539

samples/basic_discovery.py

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,29 +14,25 @@
1414

1515
allowed_actions = ['both', 'publish', 'subscribe']
1616

17-
parser = argparse.ArgumentParser()
18-
parser.add_argument('-r', '--root-ca', action='store', dest='root_ca_path', help='Root CA file path')
19-
parser.add_argument('-c', '--cert', action='store', required=True, dest='certificate_path', help='Certificate file path')
20-
parser.add_argument('-k', '--key', action='store', required=True, dest='private_key_path', help='Private key file path')
21-
parser.add_argument('-n', '--thing-name', action='store', required=True, dest='thing_name', help='Targeted thing name')
22-
parser.add_argument('-t', '--topic', action='store', dest='topic', default='test/topic', help='Targeted topic')
23-
parser.add_argument('-m', '--mode', action='store', dest='mode', default='both',
24-
help='Operation modes: %s'%str(allowed_actions))
25-
parser.add_argument('-M', '--message', action='store', dest='message', default='Hello World!',
26-
help='Message to publish')
27-
parser.add_argument('--region', action='store', dest='region', default='us-east-1')
28-
parser.add_argument('--max-pub-ops', action='store', dest='max_pub_ops', default=10)
29-
parser.add_argument('--print-discover-resp-only', action='store_true', dest='print_discover_resp_only', default=False)
30-
parser.add_argument('-v', '--verbosity', choices=[x.name for x in LogLevel], default=LogLevel.NoLogs.name,
31-
help='Logging level')
32-
33-
args = parser.parse_args()
17+
# Parse arguments
18+
import command_line_utils;
19+
cmdUtils = command_line_utils.CommandLineUtils("Basic Discovery - Greengrass discovery example.")
20+
cmdUtils.add_common_mqtt_commands()
21+
cmdUtils.add_common_topic_message_commands()
22+
cmdUtils.add_common_logging_commands()
23+
cmdUtils.remove_command("endpoint")
24+
cmdUtils.register_command("thing_name", "<str>", "The name assigned to your IoT Thing", required=True)
25+
cmdUtils.register_command("mode", "<mode>", "The operation mode (optional, default='both').\nModes:%s"%str(allowed_actions), default='both')
26+
cmdUtils.register_command("region", "<str>", "The region to connect through (optional, default='us-east-1').", default="us-east-1")
27+
cmdUtils.register_command("max_pub_ops", "<int>", "The maximum number of publish operations (optional, default='10').", default=10, type=int)
28+
cmdUtils.register_command("print_discover_resp_only", "", "(optional, default='False').", default=False, type=bool, action="store_true")
29+
args = cmdUtils.get_args()
3430

3531
io.init_logging(getattr(LogLevel, args.verbosity), 'stderr')
3632

37-
tls_options = io.TlsContextOptions.create_client_with_mtls_from_path(args.certificate_path, args.private_key_path)
38-
if args.root_ca_path:
39-
tls_options.override_default_trust_store_from_path(None, args.root_ca_path)
33+
tls_options = io.TlsContextOptions.create_client_with_mtls_from_path(args.cert, args.key)
34+
if args.ca_file:
35+
tls_options.override_default_trust_store_from_path(None, args.ca_file)
4036
tls_context = io.ClientTlsContext(tls_options)
4137

4238
socket_options = io.SocketOptions()
@@ -69,8 +65,8 @@ def try_iot_endpoints():
6965
mqtt_connection = mqtt_connection_builder.mtls_from_path(
7066
endpoint=connectivity_info.host_address,
7167
port=connectivity_info.port,
72-
cert_filepath=args.certificate_path,
73-
pri_key_filepath=args.private_key_path,
68+
cert_filepath=args.cert,
69+
pri_key_filepath=args.key,
7470
ca_bytes=gg_group.certificate_authorities[0].encode('utf-8'),
7571
on_connection_interrupted=on_connection_interupted,
7672
on_connection_resumed=on_connection_resumed,

samples/command_line_utils.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0.
3+
4+
import argparse
5+
from awscrt import io
6+
7+
class CommandLineUtils:
8+
def __init__(self, description) -> None:
9+
self.parser = argparse.ArgumentParser(description="Send and receive messages through and MQTT connection.")
10+
self.commands = {}
11+
12+
def register_command(self, command_name, example_input, help_output, required=False, type=None, default=None, choices=None, action=None):
13+
self.commands[command_name] = {
14+
"name":command_name,
15+
"example_input":example_input,
16+
"help_output":help_output,
17+
"required": required,
18+
"type": type,
19+
"default": default,
20+
"choices": choices,
21+
"action": action
22+
}
23+
24+
def remove_command(self, command_name):
25+
if command_name in self.commands.keys():
26+
self.commands.pop(command_name)
27+
28+
def get_args(self):
29+
# add all the commands
30+
for command in self.commands.values():
31+
if not command["action"] is None:
32+
self.parser.add_argument("--" + command["name"], action=command["action"], help=command["help_output"],
33+
required=command["required"], default=command["default"])
34+
else:
35+
self.parser.add_argument("--" + command["name"], metavar=command["example_input"], help=command["help_output"],
36+
required=command["required"], type=command["type"], default=command["default"], choices=command["choices"])
37+
38+
return self.parser.parse_args()
39+
40+
def update_command(self, command_name, new_example_input=None, new_help_output=None, new_required=None, new_type=None, new_default=None, new_action=None):
41+
if command_name in self.commands.keys():
42+
if new_example_input:
43+
self.commands[command_name]["example_input"] = new_example_input
44+
if new_help_output:
45+
self.commands[command_name]["help_output"] = new_help_output
46+
if new_required:
47+
self.commands[command_name]["required"] = new_required
48+
if new_type:
49+
self.commands[command_name]["type"] = new_type
50+
if new_default:
51+
self.commands[command_name]["default"] = new_default
52+
if new_action:
53+
self.commands[command_name]["action"] = new_action
54+
55+
def add_common_mqtt_commands(self):
56+
self.register_command("endpoint", "<str>", "The endpoint of the mqtt server not including a port.", True, str)
57+
self.register_command("key", "<path>", "Path to your key in PEM format.", False, str)
58+
self.register_command("cert", "<path>", "Path to your client certificate in PEM format.", False, str)
59+
self.register_command("ca_file", "<path>", "Path to AmazonRootCA1.pem (optional, system trust store used by default)", False, str)
60+
61+
def add_common_proxy_commands(self):
62+
self.register_command("proxy_host", "<str>", "Host name of the proxy server to connect through (optional)", False, str)
63+
self.register_command("proxy_port", "<int>", "Port of the http proxy to use (optional, default='8080')", type=int, default=8080)
64+
65+
def add_common_topic_message_commands(self):
66+
self.register_command("topic", "<str>", "Topic to publish, subscribe to (optional, default='test/topic').", default="test/topic")
67+
self.register_command("message", "<str>", "The message to send in the payload (optional, default='Hello World!').", default="Hello World!")
68+
69+
def add_common_websocket_commands(self):
70+
self.register_command("use_websocket", "", "If specified, uses a websocket over https (optional).", default=False, action="store_true")
71+
self.register_command("signing_region", "<str>",
72+
"Used for websocket signer. It should only be specified if websockets are used (optional, default='us-east-1')", default="us-east-1")
73+
74+
def add_common_logging_commands(self):
75+
self.register_command("verbosity", "<Log Level>", "Logging level.", default=io.LogLevel.NoLogs.name, choices=[x.name for x in io.LogLevel])

samples/fleetprovisioning.py

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -26,31 +26,22 @@
2626
# On startup, the script subscribes to topics based on the request type of either CSR or Keys
2727
# publishes the request to corresponding topic and calls RegisterThing.
2828

29-
parser = argparse.ArgumentParser(description="Fleet Provisioning sample script.")
30-
parser.add_argument('--endpoint', required=True, help="Your AWS IoT custom endpoint, not including a port. " +
31-
"Ex: \"w6zbse3vjd5b4p-ats.iot.us-west-2.amazonaws.com\"")
32-
parser.add_argument('--cert', help="File path to your client certificate, in PEM format")
33-
parser.add_argument('--key', help="File path to your private key file, in PEM format")
34-
parser.add_argument('--root-ca', help="File path to root certificate authority, in PEM format. " +
35-
"Necessary if MQTT server uses a certificate that's not already in " +
36-
"your trust store")
37-
parser.add_argument('--client-id', default="test-" + str(uuid4()), help="Client ID for MQTT connection.")
38-
parser.add_argument('--use-websocket', default=False, action='store_true',
39-
help="To use a websocket instead of raw mqtt. If you " +
40-
"specify this option you must specify a region for signing.")
41-
parser.add_argument('--signing-region', default='us-east-1', help="If you specify --use-web-socket, this " +
42-
"is the region that will be used for computing the Sigv4 signature")
43-
parser.add_argument('--proxy-host', help="Hostname of proxy to connect to.")
44-
parser.add_argument('--proxy-port', type=int, default=8080, help="Port of proxy to connect to.")
45-
parser.add_argument('--verbosity', choices=[x.name for x in io.LogLevel], default=io.LogLevel.NoLogs.name,
46-
help='Logging level')
47-
parser.add_argument("--csr", help="File path to your client CSR in PEM format")
48-
parser.add_argument("--templateName", help="Template name")
49-
parser.add_argument("--templateParameters", help="Values for Template Parameters")
29+
# Parse arguments
30+
import command_line_utils;
31+
cmdUtils = command_line_utils.CommandLineUtils("Fleet Provisioning - Provision device using either the keys or CSR.")
32+
cmdUtils.add_common_mqtt_commands()
33+
cmdUtils.add_common_websocket_commands()
34+
cmdUtils.add_common_proxy_commands()
35+
cmdUtils.add_common_logging_commands()
36+
cmdUtils.register_command("client_id", "<str>", "Client ID to use for MQTT connection (optional, default='test-*').", default="test-" + str(uuid4()))
37+
cmdUtils.register_command("csr", "<path>", "Path to CSR in Pem format (optional).")
38+
cmdUtils.register_command("template_name", "<str>", "The name of your provisioning template.")
39+
cmdUtils.register_command("template_parameters", "<json>", "Template parameters json.")
40+
cmdUtils.update_command("cert", new_help_output="Path to your certificate in PEM format. If this is not set you must specify use_websocket")
41+
args = cmdUtils.get_args()
5042

5143
# Using globals to simplify sample code
5244
is_sample_done = threading.Event()
53-
args = parser.parse_args()
5445

5546
io.init_logging(getattr(io.LogLevel, args.verbosity), 'stderr')
5647
mqtt_connection = None
@@ -240,7 +231,7 @@ def waitForRegisterThingResponse():
240231
http_proxy_options=proxy_options,
241232
on_connection_interrupted=on_connection_interrupted,
242233
on_connection_resumed=on_connection_resumed,
243-
ca_filepath=args.root_ca,
234+
ca_filepath=args.ca_file,
244235
client_id=args.client_id,
245236
clean_session=False,
246237
keep_alive_secs=30)
@@ -250,7 +241,7 @@ def waitForRegisterThingResponse():
250241
endpoint=args.endpoint,
251242
cert_filepath=args.cert,
252243
pri_key_filepath=args.key,
253-
ca_filepath=args.root_ca,
244+
ca_filepath=args.ca_file,
254245
client_id=args.client_id,
255246
on_connection_interrupted=on_connection_interrupted,
256247
on_connection_resumed=on_connection_resumed,
@@ -321,7 +312,7 @@ def waitForRegisterThingResponse():
321312
createcertificatefromcsr_subscribed_rejected_future.result()
322313

323314

324-
registerthing_subscription_request = iotidentity.RegisterThingSubscriptionRequest(template_name=args.templateName)
315+
registerthing_subscription_request = iotidentity.RegisterThingSubscriptionRequest(template_name=args.template_name)
325316

326317
print("Subscribing to RegisterThing Accepted topic...")
327318
registerthing_subscribed_accepted_future, _ = identity_client.subscribe_to_register_thing_accepted(
@@ -352,9 +343,9 @@ def waitForRegisterThingResponse():
352343
raise Exception('CreateKeysAndCertificate API did not succeed')
353344

354345
registerThingRequest = iotidentity.RegisterThingRequest(
355-
template_name=args.templateName,
346+
template_name=args.template_name,
356347
certificate_ownership_token=createKeysAndCertificateResponse.certificate_ownership_token,
357-
parameters=json.loads(args.templateParameters))
348+
parameters=json.loads(args.template_parameters))
358349
else:
359350
print("Publishing to CreateCertificateFromCsr...")
360351
csrPath = open(args.csr, 'r').read()
@@ -369,9 +360,9 @@ def waitForRegisterThingResponse():
369360
raise Exception('CreateCertificateFromCsr API did not succeed')
370361

371362
registerThingRequest = iotidentity.RegisterThingRequest(
372-
template_name=args.templateName,
363+
template_name=args.template_name,
373364
certificate_ownership_token=createCertificateFromCsrResponse.certificate_ownership_token,
374-
parameters=json.loads(args.templateParameters))
365+
parameters=json.loads(args.template_parameters))
375366

376367
print("Publishing to RegisterThing topic...")
377368
registerthing_publish_future = identity_client.publish_register_thing(registerThingRequest, mqtt.QoS.AT_LEAST_ONCE)

0 commit comments

Comments
 (0)