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

Commit

Permalink
Browse files Browse the repository at this point in the history
* Initial commit.

* Changes oauth2 client

* Fixes google.auth and addresses changes in v1beta1
  • Loading branch information
gguuss authored and dpebot committed May 25, 2017
0 parents commit 37d3f63
Show file tree
Hide file tree
Showing 3 changed files with 383 additions and 0 deletions.
43 changes: 43 additions & 0 deletions samples/api-client/manager/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Cloud IoT Core Device Manager Python Sample

This sample application shows you how to manage devices programmatically using
Python.


# Setup

1. Use virtualenv to create a local Python environment.

virtualenv env && source env/bin/activate

2. Install the dependencies

pip install -r requirements.txt


# Running the sample

The following snippet summarizes usage for the device manager sample:

usage: cloudiot_device_manager_example.py [-h] \
--project_id PROJECT_ID \
--pubsub_topic PUBSUB_TOPIC \
--api_key API_KEY \
[--ec_public_key_file EC_PUBLIC_KEY_FILE] \
[--rsa_certificate_file RSA_CERTIFICATE_FILE] \
[--cloud_region CLOUD_REGION] \
[--service_account_json SERVICE_ACCOUNT_JSON] \
[--registry_id REGISTRY_ID]


For example, if your project-id is `blue-jet-123` and your service account
credentials are stored in `creds.json` in your home folder, the following
command would run the sample:

python cloudiot_device_manager_example.py \
--project_id blue-jet-123 \
--pubsub_topic projects/blue-jet-123/topics/device-events \
--ec_public_key ../ec_public.pem \
--rsa_certificate_file ../rsa_cert.pem \
--api_key YOUR_API_KEY \
--service_account_json $HOME/creds.json
337 changes: 337 additions & 0 deletions samples/api-client/manager/cloudiot_device_manager_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,337 @@
#!/usr/bin/env python

# Copyright 2017 Google Inc. 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.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License 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.


"""Example of using the Google Cloud IoT Core device manager to administer
devices.
This example uses the Device Manager API to create, retrieve, disable, list and
delete Cloud IoT Core devices and registries, using both RSA and eliptic curve
keys for authentication.
Before you run the sample, configure Cloud IoT Core as described in the
documentation at https://cloud.google.com/iot or by following the instructions
in the README located in the parent folder.
Usage example:
$ python cloudiot_device_manager_example.py \
--project_id=my-project-id \
--pubsub_topic=projects/my-project-id/topics/my-topic-id \
--api_key=YOUR_API_KEY \
--ec_public_key_file=ec_public.pem \
--rsa_certificate_file=rsa_cert.pem \
--service_account_json=service_account.json
Troubleshooting:
- If you get a 400 error when running the example, with the message "The API
Key and the authentication credential are from different projects" it means
that you are using the wrong API Key. Ensure that you are using the API key
from Google Cloud Platform's API Manager's Credentials page.
"""

import argparse
import sys
import time

from google.oauth2 import service_account
from googleapiclient import discovery
from googleapiclient.errors import HttpError

API_SCOPES = ['https://www.googleapis.com/auth/cloud-platform']
API_VERSION = 'v1beta1'
DISCOVERY_API = 'https://cloudiot.googleapis.com/$discovery/rest'
SERVICE_NAME = 'cloudiot'


def discovery_url(api_key):
"""Construct the discovery url for the given api key."""
return '{}?version={}&key={}'.format(DISCOVERY_API, API_VERSION, api_key)


class DeviceRegistry(object):
"""Administer a set of devices for a device registry."""

def __init__(
self, project_id, registry_id, cloud_region,
service_account_json, api_key, pubsub_topic):
"""Lookup or create a device registry for the given project."""
self.parent = 'projects/{}/locations/{}'.format(
project_id, cloud_region)
self.full_name = '{}/registries/{}'.format(self.parent, registry_id)
credentials = service_account.Credentials.from_service_account_file(
service_account_json)
scoped_credentials = credentials.with_scopes(API_SCOPES)

if not credentials:
sys.exit(
'Could not load service account credential from {}'
.format(service_account_json))

self._service = discovery.build(
SERVICE_NAME,
API_VERSION,
discoveryServiceUrl=discovery_url(api_key),
credentials=scoped_credentials)

# Lookup or create the device registry. Here we bind the registry to
# the given Cloud Pub/Sub topic. All devices within a registry will
# have their telemetry data published to this topic, using attributes
# to indicate which device the data originated from.
body = {
'eventNotificationConfig': {
'pubsubTopicName': pubsub_topic
},
'id': registry_id
}
request = self._service.projects().locations().registries().create(
parent=self.parent, body=body)

try:
response = request.execute()
print('Created registry', registry_id)
print(response)
except HttpError as e:
if e.resp.status == 409:
# Device registry already exists
print(
'Registry', registry_id,
'already exists - looking it up instead.')
request = self._service.projects().locations().registries(
).get(name=self.full_name)
request.execute()

else:
raise

def delete(self):
"""Delete this registry."""
request = self._service.projects().locations().registries().delete(
name=self.full_name)
return request.execute()

def list_devices(self):
"""List all devices in the registry."""
request = self._service.projects().locations().registries().devices(
).list(parent=self.full_name)
response = request.execute()
return response.get('devices', [])

def _create_device(self, device_template):
request = self._service.projects().locations().registries().devices(
).create(parent=self.full_name, body=device_template)
return request.execute()

def create_device_with_rs256(self, device_id, certificate_file):
"""Create a new device with the given id, using RS256 for
authentication."""
with open(certificate_file) as f:
certificate = f.read()

# Create a device with the given certificate. Note that you can have
# multiple credentials associated with a device.
device_template = {
'id': device_id,
'credentials': [{
'publicKey': {
'format': 'RSA_X509_PEM',
'key': certificate
}
}]
}
return self._create_device(device_template)

def create_device_with_es256(self, device_id, public_key_file):
"""Create a new device with the given id, using ES256 for
authentication."""
with open(public_key_file) as f:
public_key = f.read()

# Create a device with the given public key. Note that you can have
# multiple credentials associated with a device.
device_template = {
'id': device_id,
'credentials': [{
'publicKey': {
'format': 'ES256_PEM',
'key': public_key
}
}]
}
return self._create_device(device_template)

def create_device_with_no_auth(self, device_id):
"""Create a new device with no authentication."""
device_template = {
'id': device_id,
}
return self._create_device(device_template)

def patch_es256_for_auth(self, device_id, public_key_file):
"""Patch the device to add an ES256 public key to the device."""
with open(public_key_file) as f:
public_key = f.read()

patch = {
'credentials': [{
'publicKey': {
'format': 'ES256_PEM',
'key': public_key
}
}]
}

device_name = '{}/devices/{}'.format(self.full_name, device_id)

# Patch requests use a FieldMask to determine which fields to update.
# In this case, we're updating the device's credentials with a new
# entry.
request = self._service.projects().locations().registries().devices(
).patch(name=device_name, updateMask='credentials', body=patch)

return request.execute()

def delete_device(self, device_id):
"""Delete the device with the given id."""
device_name = '{}/devices/{}'.format(self.full_name, device_id)
request = self._service.projects().locations().registries().devices(
).delete(name=device_name)
return request.execute()


def parse_command_line_args():
"""Parse command line arguments."""
parser = argparse.ArgumentParser(
description='Example of Google Cloud IoT Core device management.')
# Required arguments
parser.add_argument(
'--project_id', required=True, help='GCP cloud project name.')
parser.add_argument(
'--pubsub_topic',
required=True,
help=('Google Cloud Pub/Sub topic. '
'Format is projects/project_id/topics/topic-id'))
parser.add_argument('--api_key', required=True, help='Your API key.')

# Optional arguments
parser.add_argument(
'--ec_public_key_file',
default='ec_public.pem',
help='Path to public ES256 key file.')
parser.add_argument(
'--rsa_certificate_file',
default='rsa_cert.pem',
help='Path to RS256 certificate file.')
parser.add_argument(
'--cloud_region', default='us-central1', help='GCP cloud region')
parser.add_argument(
'--service_account_json',
default='service_account.json',
help='Path to service account json file.')
parser.add_argument(
'--registry_id',
default=None,
help='Registry id. If not set, a name will be generated.')

return parser.parse_args()


def main():
args = parse_command_line_args()

# The example id for our registry.
if args.registry_id is None:
registry_id = 'cloudiot_device_manager_example_registry_{}'.format(
int(time.time()))
else:
registry_id = args.registry_id

# Lookup or create the registry.
print 'Creating registry', registry_id, 'in project', args.project_id
device_registry = DeviceRegistry(
args.project_id, registry_id, args.cloud_region,
args.service_account_json, args.api_key, args.pubsub_topic)

# List devices for the (empty) registry
print('Current devices in the registry:')
for device in device_registry.list_devices():
print device

# Create an RS256 authenticated device. Note that for security, it is very
# important that you use unique public/private key pairs for each device
# (do not reuse a key pair for multiple devices). This way if a private key
# is compromised, only a single device will be affected.
rs256_device_id = 'rs256-device'
print('Creating RS256 authenticated device', rs256_device_id)
device_registry.create_device_with_rs256(
rs256_device_id, args.rsa_certificate_file)

# Create an ES256 authenticated device. To demonstrate updating a device,
# we will create the device with no authentication, and then update it to
# use ES256 for authentication. Note that while one can create a device
# without authentication, the MQTT client will not be able to connect to
# it.
es256_device_id = 'es256-device'
print('Creating device without authentication', es256_device_id)
device_registry.create_device_with_no_auth(es256_device_id)

# Now list devices again
print('Current devices in the registry:')
for device in device_registry.list_devices():
print(device)

# Patch the device with authentication
print('Updating device', es256_device_id, 'to use ES256 authentication.')
device_registry.patch_es256_for_auth(
es256_device_id, args.ec_public_key_file)

# Now list devices again
print('Current devices in the registry:')
for device in device_registry.list_devices():
print(device)

# Delete the ES256 device
print('Deleting device', es256_device_id)
device_registry.delete_device(es256_device_id)

# List devices - will only show the RS256 device.
print('Current devices in the registry:')
for device in device_registry.list_devices():
print(device)

# Try to delete the registry. This will fail however, since the registry is
# not empty.
print('Trying to delete non-empty registry')
try:
device_registry.delete()
except HttpError as e:
# This will say that the registry is not empty.
print(e)

# Delete the RSA devices from the registry
print('Deleting device', rs256_device_id)
device_registry.delete_device(rs256_device_id)

# Now actually delete registry
print('Deleting registry')
device_registry.delete()

print 'Completed successfully. Goodbye!'


if __name__ == '__main__':
main()
3 changes: 3 additions & 0 deletions samples/api-client/manager/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
google-api-python-client==1.6.2
google-auth-httplib2==0.0.2
google-auth==1.0.1

0 comments on commit 37d3f63

Please sign in to comment.