Skip to content
This repository was archived by the owner on Oct 29, 2023. It is now read-only.

Commit 37d3f63

Browse files
gguussdpebot
authored andcommitted
* Initial commit. * Changes oauth2 client * Fixes google.auth and addresses changes in v1beta1
0 parents  commit 37d3f63

File tree

3 files changed

+383
-0
lines changed

3 files changed

+383
-0
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Cloud IoT Core Device Manager Python Sample
2+
3+
This sample application shows you how to manage devices programmatically using
4+
Python.
5+
6+
7+
# Setup
8+
9+
1. Use virtualenv to create a local Python environment.
10+
11+
virtualenv env && source env/bin/activate
12+
13+
2. Install the dependencies
14+
15+
pip install -r requirements.txt
16+
17+
18+
# Running the sample
19+
20+
The following snippet summarizes usage for the device manager sample:
21+
22+
usage: cloudiot_device_manager_example.py [-h] \
23+
--project_id PROJECT_ID \
24+
--pubsub_topic PUBSUB_TOPIC \
25+
--api_key API_KEY \
26+
[--ec_public_key_file EC_PUBLIC_KEY_FILE] \
27+
[--rsa_certificate_file RSA_CERTIFICATE_FILE] \
28+
[--cloud_region CLOUD_REGION] \
29+
[--service_account_json SERVICE_ACCOUNT_JSON] \
30+
[--registry_id REGISTRY_ID]
31+
32+
33+
For example, if your project-id is `blue-jet-123` and your service account
34+
credentials are stored in `creds.json` in your home folder, the following
35+
command would run the sample:
36+
37+
python cloudiot_device_manager_example.py \
38+
--project_id blue-jet-123 \
39+
--pubsub_topic projects/blue-jet-123/topics/device-events \
40+
--ec_public_key ../ec_public.pem \
41+
--rsa_certificate_file ../rsa_cert.pem \
42+
--api_key YOUR_API_KEY \
43+
--service_account_json $HOME/creds.json
Lines changed: 337 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,337 @@
1+
#!/usr/bin/env python
2+
3+
# Copyright 2017 Google Inc. All Rights Reserved.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
18+
"""Example of using the Google Cloud IoT Core device manager to administer
19+
devices.
20+
21+
This example uses the Device Manager API to create, retrieve, disable, list and
22+
delete Cloud IoT Core devices and registries, using both RSA and eliptic curve
23+
keys for authentication.
24+
25+
Before you run the sample, configure Cloud IoT Core as described in the
26+
documentation at https://cloud.google.com/iot or by following the instructions
27+
in the README located in the parent folder.
28+
29+
Usage example:
30+
31+
$ python cloudiot_device_manager_example.py \
32+
--project_id=my-project-id \
33+
--pubsub_topic=projects/my-project-id/topics/my-topic-id \
34+
--api_key=YOUR_API_KEY \
35+
--ec_public_key_file=ec_public.pem \
36+
--rsa_certificate_file=rsa_cert.pem \
37+
--service_account_json=service_account.json
38+
39+
Troubleshooting:
40+
41+
- If you get a 400 error when running the example, with the message "The API
42+
Key and the authentication credential are from different projects" it means
43+
that you are using the wrong API Key. Ensure that you are using the API key
44+
from Google Cloud Platform's API Manager's Credentials page.
45+
"""
46+
47+
import argparse
48+
import sys
49+
import time
50+
51+
from google.oauth2 import service_account
52+
from googleapiclient import discovery
53+
from googleapiclient.errors import HttpError
54+
55+
API_SCOPES = ['https://www.googleapis.com/auth/cloud-platform']
56+
API_VERSION = 'v1beta1'
57+
DISCOVERY_API = 'https://cloudiot.googleapis.com/$discovery/rest'
58+
SERVICE_NAME = 'cloudiot'
59+
60+
61+
def discovery_url(api_key):
62+
"""Construct the discovery url for the given api key."""
63+
return '{}?version={}&key={}'.format(DISCOVERY_API, API_VERSION, api_key)
64+
65+
66+
class DeviceRegistry(object):
67+
"""Administer a set of devices for a device registry."""
68+
69+
def __init__(
70+
self, project_id, registry_id, cloud_region,
71+
service_account_json, api_key, pubsub_topic):
72+
"""Lookup or create a device registry for the given project."""
73+
self.parent = 'projects/{}/locations/{}'.format(
74+
project_id, cloud_region)
75+
self.full_name = '{}/registries/{}'.format(self.parent, registry_id)
76+
credentials = service_account.Credentials.from_service_account_file(
77+
service_account_json)
78+
scoped_credentials = credentials.with_scopes(API_SCOPES)
79+
80+
if not credentials:
81+
sys.exit(
82+
'Could not load service account credential from {}'
83+
.format(service_account_json))
84+
85+
self._service = discovery.build(
86+
SERVICE_NAME,
87+
API_VERSION,
88+
discoveryServiceUrl=discovery_url(api_key),
89+
credentials=scoped_credentials)
90+
91+
# Lookup or create the device registry. Here we bind the registry to
92+
# the given Cloud Pub/Sub topic. All devices within a registry will
93+
# have their telemetry data published to this topic, using attributes
94+
# to indicate which device the data originated from.
95+
body = {
96+
'eventNotificationConfig': {
97+
'pubsubTopicName': pubsub_topic
98+
},
99+
'id': registry_id
100+
}
101+
request = self._service.projects().locations().registries().create(
102+
parent=self.parent, body=body)
103+
104+
try:
105+
response = request.execute()
106+
print('Created registry', registry_id)
107+
print(response)
108+
except HttpError as e:
109+
if e.resp.status == 409:
110+
# Device registry already exists
111+
print(
112+
'Registry', registry_id,
113+
'already exists - looking it up instead.')
114+
request = self._service.projects().locations().registries(
115+
).get(name=self.full_name)
116+
request.execute()
117+
118+
else:
119+
raise
120+
121+
def delete(self):
122+
"""Delete this registry."""
123+
request = self._service.projects().locations().registries().delete(
124+
name=self.full_name)
125+
return request.execute()
126+
127+
def list_devices(self):
128+
"""List all devices in the registry."""
129+
request = self._service.projects().locations().registries().devices(
130+
).list(parent=self.full_name)
131+
response = request.execute()
132+
return response.get('devices', [])
133+
134+
def _create_device(self, device_template):
135+
request = self._service.projects().locations().registries().devices(
136+
).create(parent=self.full_name, body=device_template)
137+
return request.execute()
138+
139+
def create_device_with_rs256(self, device_id, certificate_file):
140+
"""Create a new device with the given id, using RS256 for
141+
authentication."""
142+
with open(certificate_file) as f:
143+
certificate = f.read()
144+
145+
# Create a device with the given certificate. Note that you can have
146+
# multiple credentials associated with a device.
147+
device_template = {
148+
'id': device_id,
149+
'credentials': [{
150+
'publicKey': {
151+
'format': 'RSA_X509_PEM',
152+
'key': certificate
153+
}
154+
}]
155+
}
156+
return self._create_device(device_template)
157+
158+
def create_device_with_es256(self, device_id, public_key_file):
159+
"""Create a new device with the given id, using ES256 for
160+
authentication."""
161+
with open(public_key_file) as f:
162+
public_key = f.read()
163+
164+
# Create a device with the given public key. Note that you can have
165+
# multiple credentials associated with a device.
166+
device_template = {
167+
'id': device_id,
168+
'credentials': [{
169+
'publicKey': {
170+
'format': 'ES256_PEM',
171+
'key': public_key
172+
}
173+
}]
174+
}
175+
return self._create_device(device_template)
176+
177+
def create_device_with_no_auth(self, device_id):
178+
"""Create a new device with no authentication."""
179+
device_template = {
180+
'id': device_id,
181+
}
182+
return self._create_device(device_template)
183+
184+
def patch_es256_for_auth(self, device_id, public_key_file):
185+
"""Patch the device to add an ES256 public key to the device."""
186+
with open(public_key_file) as f:
187+
public_key = f.read()
188+
189+
patch = {
190+
'credentials': [{
191+
'publicKey': {
192+
'format': 'ES256_PEM',
193+
'key': public_key
194+
}
195+
}]
196+
}
197+
198+
device_name = '{}/devices/{}'.format(self.full_name, device_id)
199+
200+
# Patch requests use a FieldMask to determine which fields to update.
201+
# In this case, we're updating the device's credentials with a new
202+
# entry.
203+
request = self._service.projects().locations().registries().devices(
204+
).patch(name=device_name, updateMask='credentials', body=patch)
205+
206+
return request.execute()
207+
208+
def delete_device(self, device_id):
209+
"""Delete the device with the given id."""
210+
device_name = '{}/devices/{}'.format(self.full_name, device_id)
211+
request = self._service.projects().locations().registries().devices(
212+
).delete(name=device_name)
213+
return request.execute()
214+
215+
216+
def parse_command_line_args():
217+
"""Parse command line arguments."""
218+
parser = argparse.ArgumentParser(
219+
description='Example of Google Cloud IoT Core device management.')
220+
# Required arguments
221+
parser.add_argument(
222+
'--project_id', required=True, help='GCP cloud project name.')
223+
parser.add_argument(
224+
'--pubsub_topic',
225+
required=True,
226+
help=('Google Cloud Pub/Sub topic. '
227+
'Format is projects/project_id/topics/topic-id'))
228+
parser.add_argument('--api_key', required=True, help='Your API key.')
229+
230+
# Optional arguments
231+
parser.add_argument(
232+
'--ec_public_key_file',
233+
default='ec_public.pem',
234+
help='Path to public ES256 key file.')
235+
parser.add_argument(
236+
'--rsa_certificate_file',
237+
default='rsa_cert.pem',
238+
help='Path to RS256 certificate file.')
239+
parser.add_argument(
240+
'--cloud_region', default='us-central1', help='GCP cloud region')
241+
parser.add_argument(
242+
'--service_account_json',
243+
default='service_account.json',
244+
help='Path to service account json file.')
245+
parser.add_argument(
246+
'--registry_id',
247+
default=None,
248+
help='Registry id. If not set, a name will be generated.')
249+
250+
return parser.parse_args()
251+
252+
253+
def main():
254+
args = parse_command_line_args()
255+
256+
# The example id for our registry.
257+
if args.registry_id is None:
258+
registry_id = 'cloudiot_device_manager_example_registry_{}'.format(
259+
int(time.time()))
260+
else:
261+
registry_id = args.registry_id
262+
263+
# Lookup or create the registry.
264+
print 'Creating registry', registry_id, 'in project', args.project_id
265+
device_registry = DeviceRegistry(
266+
args.project_id, registry_id, args.cloud_region,
267+
args.service_account_json, args.api_key, args.pubsub_topic)
268+
269+
# List devices for the (empty) registry
270+
print('Current devices in the registry:')
271+
for device in device_registry.list_devices():
272+
print device
273+
274+
# Create an RS256 authenticated device. Note that for security, it is very
275+
# important that you use unique public/private key pairs for each device
276+
# (do not reuse a key pair for multiple devices). This way if a private key
277+
# is compromised, only a single device will be affected.
278+
rs256_device_id = 'rs256-device'
279+
print('Creating RS256 authenticated device', rs256_device_id)
280+
device_registry.create_device_with_rs256(
281+
rs256_device_id, args.rsa_certificate_file)
282+
283+
# Create an ES256 authenticated device. To demonstrate updating a device,
284+
# we will create the device with no authentication, and then update it to
285+
# use ES256 for authentication. Note that while one can create a device
286+
# without authentication, the MQTT client will not be able to connect to
287+
# it.
288+
es256_device_id = 'es256-device'
289+
print('Creating device without authentication', es256_device_id)
290+
device_registry.create_device_with_no_auth(es256_device_id)
291+
292+
# Now list devices again
293+
print('Current devices in the registry:')
294+
for device in device_registry.list_devices():
295+
print(device)
296+
297+
# Patch the device with authentication
298+
print('Updating device', es256_device_id, 'to use ES256 authentication.')
299+
device_registry.patch_es256_for_auth(
300+
es256_device_id, args.ec_public_key_file)
301+
302+
# Now list devices again
303+
print('Current devices in the registry:')
304+
for device in device_registry.list_devices():
305+
print(device)
306+
307+
# Delete the ES256 device
308+
print('Deleting device', es256_device_id)
309+
device_registry.delete_device(es256_device_id)
310+
311+
# List devices - will only show the RS256 device.
312+
print('Current devices in the registry:')
313+
for device in device_registry.list_devices():
314+
print(device)
315+
316+
# Try to delete the registry. This will fail however, since the registry is
317+
# not empty.
318+
print('Trying to delete non-empty registry')
319+
try:
320+
device_registry.delete()
321+
except HttpError as e:
322+
# This will say that the registry is not empty.
323+
print(e)
324+
325+
# Delete the RSA devices from the registry
326+
print('Deleting device', rs256_device_id)
327+
device_registry.delete_device(rs256_device_id)
328+
329+
# Now actually delete registry
330+
print('Deleting registry')
331+
device_registry.delete()
332+
333+
print 'Completed successfully. Goodbye!'
334+
335+
336+
if __name__ == '__main__':
337+
main()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
google-api-python-client==1.6.2
2+
google-auth-httplib2==0.0.2
3+
google-auth==1.0.1

0 commit comments

Comments
 (0)