diff --git a/moto/iot/models.py b/moto/iot/models.py index 4bcab26eb..1279a5baa 100644 --- a/moto/iot/models.py +++ b/moto/iot/models.py @@ -1,736 +1,785 @@ -from __future__ import unicode_literals - -import hashlib -import random -import re -import string -import time -import uuid -from collections import OrderedDict -from datetime import datetime - -import boto3 - -from moto.core import BaseBackend, BaseModel -from .exceptions import ( - ResourceNotFoundException, - InvalidRequestException, - VersionConflictException -) - - -class FakeThing(BaseModel): - def __init__(self, thing_name, thing_type, attributes, region_name): - self.region_name = region_name - self.thing_name = thing_name - self.thing_type = thing_type - self.attributes = attributes - self.arn = 'arn:aws:iot:%s:1:thing/%s' % (self.region_name, thing_name) - self.version = 1 - # TODO: we need to handle 'version'? - - # for iot-data - self.thing_shadow = None - - def to_dict(self, include_default_client_id=False): - obj = { - 'thingName': self.thing_name, - 'thingArn': self.arn, - 'attributes': self.attributes, - 'version': self.version - } - if self.thing_type: - obj['thingTypeName'] = self.thing_type.thing_type_name - if include_default_client_id: - obj['defaultClientId'] = self.thing_name - return obj - - -class FakeThingType(BaseModel): - def __init__(self, thing_type_name, thing_type_properties, region_name): - self.region_name = region_name - self.thing_type_name = thing_type_name - self.thing_type_properties = thing_type_properties - self.thing_type_id = str(uuid.uuid4()) # I don't know the rule of id - t = time.time() - self.metadata = { - 'deprecated': False, - 'creationData': int(t * 1000) / 1000.0 - } - self.arn = 'arn:aws:iot:%s:1:thingtype/%s' % (self.region_name, thing_type_name) - - def to_dict(self): - return { - 'thingTypeName': self.thing_type_name, - 'thingTypeId': self.thing_type_id, - 'thingTypeProperties': self.thing_type_properties, - 'thingTypeMetadata': self.metadata - } - - -class FakeThingGroup(BaseModel): - def __init__(self, thing_group_name, parent_group_name, thing_group_properties, region_name): - self.region_name = region_name - self.thing_group_name = thing_group_name - self.thing_group_id = str(uuid.uuid4()) # I don't know the rule of id - self.version = 1 # TODO: tmp - self.parent_group_name = parent_group_name - self.thing_group_properties = thing_group_properties or {} - t = time.time() - self.metadata = { - 'creationData': int(t * 1000) / 1000.0 - } - self.arn = 'arn:aws:iot:%s:1:thinggroup/%s' % (self.region_name, thing_group_name) - self.things = OrderedDict() - - def to_dict(self): - return { - 'thingGroupName': self.thing_group_name, - 'thingGroupId': self.thing_group_id, - 'version': self.version, - 'thingGroupProperties': self.thing_group_properties, - 'thingGroupMetadata': self.metadata - } - - -class FakeCertificate(BaseModel): - def __init__(self, certificate_pem, status, region_name): - m = hashlib.sha256() - m.update(str(uuid.uuid4()).encode('utf-8')) - self.certificate_id = m.hexdigest() - self.arn = 'arn:aws:iot:%s:1:cert/%s' % (region_name, self.certificate_id) - self.certificate_pem = certificate_pem - self.status = status - - # TODO: must adjust - self.owner = '1' - self.transfer_data = {} - self.creation_date = time.time() - self.last_modified_date = self.creation_date - self.ca_certificate_id = None - - def to_dict(self): - return { - 'certificateArn': self.arn, - 'certificateId': self.certificate_id, - 'status': self.status, - 'creationDate': self.creation_date - } - - def to_description_dict(self): - """ - You might need keys below in some situation - - caCertificateId - - previousOwnedBy - """ - return { - 'certificateArn': self.arn, - 'certificateId': self.certificate_id, - 'status': self.status, - 'certificatePem': self.certificate_pem, - 'ownedBy': self.owner, - 'creationDate': self.creation_date, - 'lastModifiedDate': self.last_modified_date, - 'transferData': self.transfer_data - } - - -class FakePolicy(BaseModel): - def __init__(self, name, document, region_name, default_version_id='1'): - self.name = name - self.document = document - self.arn = 'arn:aws:iot:%s:1:policy/%s' % (region_name, name) - self.default_version_id = default_version_id - self.versions = [FakePolicyVersion(self.name, document, True, region_name)] - - def to_get_dict(self): - return { - 'policyName': self.name, - 'policyArn': self.arn, - 'policyDocument': self.document, - 'defaultVersionId': self.default_version_id - } - - def to_dict_at_creation(self): - return { - 'policyName': self.name, - 'policyArn': self.arn, - 'policyDocument': self.document, - 'policyVersionId': self.default_version_id - } - - def to_dict(self): - return { - 'policyName': self.name, - 'policyArn': self.arn, - } - - -class FakePolicyVersion(object): - - def __init__(self, - policy_name, - document, - is_default, - region_name): - self.name = policy_name - self.arn = 'arn:aws:iot:%s:1:policy/%s' % (region_name, policy_name) - self.document = document or {} - self.is_default = is_default - self.version_id = '1' - - self.create_datetime = time.mktime(datetime(2015, 1, 1).timetuple()) - self.last_modified_datetime = time.mktime(datetime(2015, 1, 2).timetuple()) - - def to_get_dict(self): - return { - 'policyName': self.name, - 'policyArn': self.arn, - 'policyDocument': self.document, - 'policyVersionId': self.version_id, - 'isDefaultVersion': self.is_default, - 'creationDate': self.create_datetime, - 'lastModifiedDate': self.last_modified_datetime, - 'generationId': self.version_id - } - - def to_dict_at_creation(self): - return { - 'policyArn': self.arn, - 'policyDocument': self.document, - 'policyVersionId': self.version_id, - 'isDefaultVersion': self.is_default - } - - def to_dict(self): - return { - 'versionId': self.version_id, - 'isDefaultVersion': self.is_default, - 'createDate': self.create_datetime, - } - - -class FakeJob(BaseModel): - JOB_ID_REGEX_PATTERN = "[a-zA-Z0-9_-]" - JOB_ID_REGEX = re.compile(JOB_ID_REGEX_PATTERN) - - def __init__(self, job_id, targets, document_source, document, description, presigned_url_config, target_selection, - job_executions_rollout_config, document_parameters, region_name): - if not self._job_id_matcher(self.JOB_ID_REGEX, job_id): - raise InvalidRequestException() - - self.region_name = region_name - self.job_id = job_id - self.job_arn = 'arn:aws:iot:%s:1:job/%s' % (self.region_name, job_id) - self.targets = targets - self.document_source = document_source - self.document = document - self.description = description - self.presigned_url_config = presigned_url_config - self.target_selection = target_selection - self.job_executions_rollout_config = job_executions_rollout_config - self.status = None # IN_PROGRESS | CANCELED | COMPLETED - self.comment = None - self.created_at = time.mktime(datetime(2015, 1, 1).timetuple()) - self.last_updated_at = time.mktime(datetime(2015, 1, 1).timetuple()) - self.completed_at = None - self.job_process_details = { - 'processingTargets': targets, - 'numberOfQueuedThings': 1, - 'numberOfCanceledThings': 0, - 'numberOfSucceededThings': 0, - 'numberOfFailedThings': 0, - 'numberOfRejectedThings': 0, - 'numberOfInProgressThings': 0, - 'numberOfRemovedThings': 0 - } - self.document_parameters = document_parameters - - def to_dict(self): - obj = { - 'jobArn': self.job_arn, - 'jobId': self.job_id, - 'targets': self.targets, - 'description': self.description, - 'presignedUrlConfig': self.presigned_url_config, - 'targetSelection': self.target_selection, - 'jobExecutionsRolloutConfig': self.job_executions_rollout_config, - 'status': self.status, - 'comment': self.comment, - 'createdAt': self.created_at, - 'lastUpdatedAt': self.last_updated_at, - 'completedAt': self.completedAt, - 'jobProcessDetails': self.job_process_details, - 'documentParameters': self.document_parameters, - 'document': self.document, - 'documentSource': self.document_source - } - - return obj - - def _job_id_matcher(self, regex, argument): - regex_match = regex.match(argument) - length_match = len(argument) <= 64 - return regex_match and length_match - - -class IoTBackend(BaseBackend): - def __init__(self, region_name=None): - super(IoTBackend, self).__init__() - self.region_name = region_name - self.things = OrderedDict() - self.jobs = OrderedDict() - self.thing_types = OrderedDict() - self.thing_groups = OrderedDict() - self.certificates = OrderedDict() - self.policies = OrderedDict() - self.principal_policies = OrderedDict() - self.principal_things = OrderedDict() - - def reset(self): - region_name = self.region_name - self.__dict__ = {} - self.__init__(region_name) - - def create_thing(self, thing_name, thing_type_name, attribute_payload): - thing_types = self.list_thing_types() - thing_type = None - if thing_type_name: - filtered_thing_types = [_ for _ in thing_types if _.thing_type_name == thing_type_name] - if len(filtered_thing_types) == 0: - raise ResourceNotFoundException() - thing_type = filtered_thing_types[0] - if attribute_payload is None: - attributes = {} - elif 'attributes' not in attribute_payload: - attributes = {} - else: - attributes = attribute_payload['attributes'] - thing = FakeThing(thing_name, thing_type, attributes, self.region_name) - self.things[thing.arn] = thing - return thing.thing_name, thing.arn - - def create_thing_type(self, thing_type_name, thing_type_properties): - if thing_type_properties is None: - thing_type_properties = {} - thing_type = FakeThingType(thing_type_name, thing_type_properties, self.region_name) - self.thing_types[thing_type.arn] = thing_type - return thing_type.thing_type_name, thing_type.arn - - def list_thing_types(self, thing_type_name=None): - if thing_type_name: - # It's weird but thing_type_name is filtered by forward match, not complete match - return [_ for _ in self.thing_types.values() if _.thing_type_name.startswith(thing_type_name)] - return self.thing_types.values() - - def list_things(self, attribute_name, attribute_value, thing_type_name, max_results, token): - all_things = [_.to_dict() for _ in self.things.values()] - if attribute_name is not None and thing_type_name is not None: - filtered_things = list(filter(lambda elem: - attribute_name in elem["attributes"] and - elem["attributes"][attribute_name] == attribute_value and - "thingTypeName" in elem and - elem["thingTypeName"] == thing_type_name, all_things)) - elif attribute_name is not None and thing_type_name is None: - filtered_things = list(filter(lambda elem: - attribute_name in elem["attributes"] and - elem["attributes"][attribute_name] == attribute_value, all_things)) - elif attribute_name is None and thing_type_name is not None: - filtered_things = list( - filter(lambda elem: "thingTypeName" in elem and elem["thingTypeName"] == thing_type_name, all_things)) - else: - filtered_things = all_things - - if token is None: - things = filtered_things[0:max_results] - next_token = str(max_results) if len(filtered_things) > max_results else None - else: - token = int(token) - things = filtered_things[token:token + max_results] - next_token = str(token + max_results) if len(filtered_things) > token + max_results else None - - return things, next_token - - def describe_thing(self, thing_name): - things = [_ for _ in self.things.values() if _.thing_name == thing_name] - if len(things) == 0: - raise ResourceNotFoundException() - return things[0] - - def describe_thing_type(self, thing_type_name): - thing_types = [_ for _ in self.thing_types.values() if _.thing_type_name == thing_type_name] - if len(thing_types) == 0: - raise ResourceNotFoundException() - return thing_types[0] - - def delete_thing(self, thing_name, expected_version): - # TODO: handle expected_version - - # can raise ResourceNotFoundError - thing = self.describe_thing(thing_name) - del self.things[thing.arn] - - def delete_thing_type(self, thing_type_name): - # can raise ResourceNotFoundError - thing_type = self.describe_thing_type(thing_type_name) - del self.thing_types[thing_type.arn] - - def update_thing(self, thing_name, thing_type_name, attribute_payload, expected_version, remove_thing_type): - # if attributes payload = {}, nothing - thing = self.describe_thing(thing_name) - thing_type = None - - if remove_thing_type and thing_type_name: - raise InvalidRequestException() - - # thing_type - if thing_type_name: - thing_types = self.list_thing_types() - filtered_thing_types = [_ for _ in thing_types if _.thing_type_name == thing_type_name] - if len(filtered_thing_types) == 0: - raise ResourceNotFoundException() - thing_type = filtered_thing_types[0] - thing.thing_type = thing_type - - if remove_thing_type: - thing.thing_type = None - - # attribute - if attribute_payload is not None and 'attributes' in attribute_payload: - do_merge = attribute_payload.get('merge', False) - attributes = attribute_payload['attributes'] - if not do_merge: - thing.attributes = attributes - else: - thing.attributes.update(attributes) - - def _random_string(self): - n = 20 - random_str = ''.join([random.choice(string.ascii_letters + string.digits) for i in range(n)]) - return random_str - - def create_keys_and_certificate(self, set_as_active): - # implement here - # caCertificate can be blank - key_pair = { - 'PublicKey': self._random_string(), - 'PrivateKey': self._random_string() - } - certificate_pem = self._random_string() - status = 'ACTIVE' if set_as_active else 'INACTIVE' - certificate = FakeCertificate(certificate_pem, status, self.region_name) - self.certificates[certificate.certificate_id] = certificate - return certificate, key_pair - - def delete_certificate(self, certificate_id): - self.describe_certificate(certificate_id) - del self.certificates[certificate_id] - - def describe_certificate(self, certificate_id): - certs = [_ for _ in self.certificates.values() if _.certificate_id == certificate_id] - if len(certs) == 0: - raise ResourceNotFoundException() - return certs[0] - - def list_certificates(self): - return self.certificates.values() - - def update_certificate(self, certificate_id, new_status): - cert = self.describe_certificate(certificate_id) - # TODO: validate new_status - cert.status = new_status - - def create_policy(self, policy_name, policy_document): - policy = FakePolicy(policy_name, policy_document, self.region_name) - self.policies[policy.name] = policy - return policy - - def attach_policy(self, policy_name, target): - principal = self._get_principal(target) - policy = self.get_policy(policy_name) - k = (target, policy_name) - if k in self.principal_policies: - return - self.principal_policies[k] = (principal, policy) - - def detach_policy(self, policy_name, target): - # this may raises ResourceNotFoundException - self._get_principal(target) - self.get_policy(policy_name) - - k = (target, policy_name) - if k not in self.principal_policies: - raise ResourceNotFoundException() - del self.principal_policies[k] - - def list_attached_policies(self, target): - policies = [v[1] for k, v in self.principal_policies.items() if k[0] == target] - return policies - - def list_policies(self): - policies = self.policies.values() - return policies - - def get_policy(self, policy_name): - policies = [_ for _ in self.policies.values() if _.name == policy_name] - if len(policies) == 0: - raise ResourceNotFoundException() - return policies[0] - - def delete_policy(self, policy_name): - policy = self.get_policy(policy_name) - del self.policies[policy.name] - - def create_policy_version(self, policy_name, policy_document, set_as_default): - policy = self.get_policy(policy_name) - if not policy: - raise ResourceNotFoundException() - version = FakePolicyVersion(policy_name, policy_document, set_as_default, self.region_name) - policy.versions.append(version) - version.version_id = '{0}'.format(len(policy.versions)) - if set_as_default: - self.set_default_policy_version(policy_name, version.version_id) - return version - - def set_default_policy_version(self, policy_name, version_id): - policy = self.get_policy(policy_name) - if not policy: - raise ResourceNotFoundException() - for version in policy.versions: - if version.version_id == version_id: - version.is_default = True - policy.default_version_id = version.version_id - policy.document = version.document - else: - version.is_default = False - - def get_policy_version(self, policy_name, version_id): - policy = self.get_policy(policy_name) - if not policy: - raise ResourceNotFoundException() - for version in policy.versions: - if version.version_id == version_id: - return version - raise ResourceNotFoundException() - - def list_policy_versions(self, policy_name): - policy = self.get_policy(policy_name) - if not policy: - raise ResourceNotFoundException() - return policy.versions - - def delete_policy_version(self, policy_name, version_id): - policy = self.get_policy(policy_name) - if not policy: - raise ResourceNotFoundException() - if version_id == policy.default_version_id: - raise InvalidRequestException( - "Cannot delete the default version of a policy") - for i, v in enumerate(policy.versions): - if v.version_id == version_id: - del policy.versions[i] - return - raise ResourceNotFoundException() - - def _get_principal(self, principal_arn): - """ - raise ResourceNotFoundException - """ - if ':cert/' in principal_arn: - certs = [_ for _ in self.certificates.values() if _.arn == principal_arn] - if len(certs) == 0: - raise ResourceNotFoundException() - principal = certs[0] - return principal - else: - # TODO: search for cognito_ids - pass - raise ResourceNotFoundException() - - def attach_principal_policy(self, policy_name, principal_arn): - principal = self._get_principal(principal_arn) - policy = self.get_policy(policy_name) - k = (principal_arn, policy_name) - if k in self.principal_policies: - return - self.principal_policies[k] = (principal, policy) - - def detach_principal_policy(self, policy_name, principal_arn): - # this may raises ResourceNotFoundException - self._get_principal(principal_arn) - self.get_policy(policy_name) - - k = (principal_arn, policy_name) - if k not in self.principal_policies: - raise ResourceNotFoundException() - del self.principal_policies[k] - - def list_principal_policies(self, principal_arn): - policies = [v[1] for k, v in self.principal_policies.items() if k[0] == principal_arn] - return policies - - def list_policy_principals(self, policy_name): - principals = [k[0] for k, v in self.principal_policies.items() if k[1] == policy_name] - return principals - - def attach_thing_principal(self, thing_name, principal_arn): - principal = self._get_principal(principal_arn) - thing = self.describe_thing(thing_name) - k = (principal_arn, thing_name) - if k in self.principal_things: - return - self.principal_things[k] = (principal, thing) - - def detach_thing_principal(self, thing_name, principal_arn): - # this may raises ResourceNotFoundException - self._get_principal(principal_arn) - self.describe_thing(thing_name) - - k = (principal_arn, thing_name) - if k not in self.principal_things: - raise ResourceNotFoundException() - del self.principal_things[k] - - def list_principal_things(self, principal_arn): - thing_names = [k[0] for k, v in self.principal_things.items() if k[0] == principal_arn] - return thing_names - - def list_thing_principals(self, thing_name): - principals = [k[0] for k, v in self.principal_things.items() if k[1] == thing_name] - return principals - - def describe_thing_group(self, thing_group_name): - thing_groups = [_ for _ in self.thing_groups.values() if _.thing_group_name == thing_group_name] - if len(thing_groups) == 0: - raise ResourceNotFoundException() - return thing_groups[0] - - def create_thing_group(self, thing_group_name, parent_group_name, thing_group_properties): - thing_group = FakeThingGroup(thing_group_name, parent_group_name, thing_group_properties, self.region_name) - self.thing_groups[thing_group.arn] = thing_group - return thing_group.thing_group_name, thing_group.arn, thing_group.thing_group_id - - def delete_thing_group(self, thing_group_name, expected_version): - thing_group = self.describe_thing_group(thing_group_name) - del self.thing_groups[thing_group.arn] - - def list_thing_groups(self, parent_group, name_prefix_filter, recursive): - thing_groups = self.thing_groups.values() - return thing_groups - - def update_thing_group(self, thing_group_name, thing_group_properties, expected_version): - thing_group = self.describe_thing_group(thing_group_name) - if expected_version and expected_version != thing_group.version: - raise VersionConflictException(thing_group_name) - attribute_payload = thing_group_properties.get('attributePayload', None) - if attribute_payload is not None and 'attributes' in attribute_payload: - do_merge = attribute_payload.get('merge', False) - attributes = attribute_payload['attributes'] - if not do_merge: - thing_group.thing_group_properties['attributePayload']['attributes'] = attributes - else: - thing_group.thing_group_properties['attributePayload']['attributes'].update(attributes) - elif attribute_payload is not None and 'attributes' not in attribute_payload: - thing_group.attributes = {} - thing_group.version = thing_group.version + 1 - return thing_group.version - - def _identify_thing_group(self, thing_group_name, thing_group_arn): - # identify thing group - if thing_group_name is None and thing_group_arn is None: - raise InvalidRequestException( - ' Both thingGroupArn and thingGroupName are empty. Need to specify at least one of them' - ) - if thing_group_name is not None: - thing_group = self.describe_thing_group(thing_group_name) - if thing_group_arn and thing_group.arn != thing_group_arn: - raise InvalidRequestException( - 'ThingGroupName thingGroupArn does not match specified thingGroupName in request' - ) - elif thing_group_arn is not None: - if thing_group_arn not in self.thing_groups: - raise InvalidRequestException() - thing_group = self.thing_groups[thing_group_arn] - return thing_group - - def _identify_thing(self, thing_name, thing_arn): - # identify thing - if thing_name is None and thing_arn is None: - raise InvalidRequestException( - 'Both thingArn and thingName are empty. Need to specify at least one of them' - ) - if thing_name is not None: - thing = self.describe_thing(thing_name) - if thing_arn and thing.arn != thing_arn: - raise InvalidRequestException( - 'ThingName thingArn does not match specified thingName in request' - ) - elif thing_arn is not None: - if thing_arn not in self.things: - raise InvalidRequestException() - thing = self.things[thing_arn] - return thing - - def add_thing_to_thing_group(self, thing_group_name, thing_group_arn, thing_name, thing_arn): - thing_group = self._identify_thing_group(thing_group_name, thing_group_arn) - thing = self._identify_thing(thing_name, thing_arn) - if thing.arn in thing_group.things: - # aws ignores duplicate registration - return - thing_group.things[thing.arn] = thing - - def remove_thing_from_thing_group(self, thing_group_name, thing_group_arn, thing_name, thing_arn): - thing_group = self._identify_thing_group(thing_group_name, thing_group_arn) - thing = self._identify_thing(thing_name, thing_arn) - if thing.arn not in thing_group.things: - # aws ignores non-registered thing - return - del thing_group.things[thing.arn] - - def list_things_in_thing_group(self, thing_group_name, recursive): - thing_group = self.describe_thing_group(thing_group_name) - return thing_group.things.values() - - def list_thing_groups_for_thing(self, thing_name): - thing = self.describe_thing(thing_name) - all_thing_groups = self.list_thing_groups(None, None, None) - ret = [] - for thing_group in all_thing_groups: - if thing.arn in thing_group.things: - ret.append({ - 'groupName': thing_group.thing_group_name, - 'groupArn': thing_group.arn - }) - return ret - - def update_thing_groups_for_thing(self, thing_name, thing_groups_to_add, thing_groups_to_remove): - thing = self.describe_thing(thing_name) - for thing_group_name in thing_groups_to_add: - thing_group = self.describe_thing_group(thing_group_name) - self.add_thing_to_thing_group( - thing_group.thing_group_name, None, - thing.thing_name, None - ) - for thing_group_name in thing_groups_to_remove: - thing_group = self.describe_thing_group(thing_group_name) - self.remove_thing_from_thing_group( - thing_group.thing_group_name, None, - thing.thing_name, None - ) - - def create_job(self, job_id, targets, document_source, document, description, presigned_url_config, - target_selection, job_executions_rollout_config, document_parameters): - job = FakeJob(job_id, targets, document_source, document, description, presigned_url_config, target_selection, - job_executions_rollout_config, document_parameters, self.region_name) - self.jobs[job_id] = job - return job.job_arn, job_id, description - - def describe_job(self, job_id): - return self.jobs[job_id] - - def get_job_document(self, job_id): - return self.jobs[job_id] - - -available_regions = boto3.session.Session().get_available_regions("iot") -iot_backends = {region: IoTBackend(region) for region in available_regions} +from __future__ import unicode_literals + +import hashlib +import random +import re +import string +import time +import uuid +from collections import OrderedDict +from datetime import datetime + +import boto3 + +from moto.core import BaseBackend, BaseModel +from .exceptions import ( + ResourceNotFoundException, + InvalidRequestException, + VersionConflictException +) + + +class FakeThing(BaseModel): + def __init__(self, thing_name, thing_type, attributes, region_name): + self.region_name = region_name + self.thing_name = thing_name + self.thing_type = thing_type + self.attributes = attributes + self.arn = 'arn:aws:iot:%s:1:thing/%s' % (self.region_name, thing_name) + self.version = 1 + # TODO: we need to handle 'version'? + + # for iot-data + self.thing_shadow = None + + def to_dict(self, include_default_client_id=False): + obj = { + 'thingName': self.thing_name, + 'thingArn': self.arn, + 'attributes': self.attributes, + 'version': self.version + } + if self.thing_type: + obj['thingTypeName'] = self.thing_type.thing_type_name + if include_default_client_id: + obj['defaultClientId'] = self.thing_name + return obj + + +class FakeThingType(BaseModel): + def __init__(self, thing_type_name, thing_type_properties, region_name): + self.region_name = region_name + self.thing_type_name = thing_type_name + self.thing_type_properties = thing_type_properties + self.thing_type_id = str(uuid.uuid4()) # I don't know the rule of id + t = time.time() + self.metadata = { + 'deprecated': False, + 'creationData': int(t * 1000) / 1000.0 + } + self.arn = 'arn:aws:iot:%s:1:thingtype/%s' % (self.region_name, thing_type_name) + + def to_dict(self): + return { + 'thingTypeName': self.thing_type_name, + 'thingTypeId': self.thing_type_id, + 'thingTypeProperties': self.thing_type_properties, + 'thingTypeMetadata': self.metadata + } + + +class FakeThingGroup(BaseModel): + def __init__(self, thing_group_name, parent_group_name, thing_group_properties, region_name): + self.region_name = region_name + self.thing_group_name = thing_group_name + self.thing_group_id = str(uuid.uuid4()) # I don't know the rule of id + self.version = 1 # TODO: tmp + self.parent_group_name = parent_group_name + self.thing_group_properties = thing_group_properties or {} + t = time.time() + self.metadata = { + 'creationData': int(t * 1000) / 1000.0 + } + self.arn = 'arn:aws:iot:%s:1:thinggroup/%s' % (self.region_name, thing_group_name) + self.things = OrderedDict() + + def to_dict(self): + return { + 'thingGroupName': self.thing_group_name, + 'thingGroupId': self.thing_group_id, + 'version': self.version, + 'thingGroupProperties': self.thing_group_properties, + 'thingGroupMetadata': self.metadata + } + + +class FakeCertificate(BaseModel): + def __init__(self, certificate_pem, status, region_name): + m = hashlib.sha256() + m.update(str(uuid.uuid4()).encode('utf-8')) + self.certificate_id = m.hexdigest() + self.arn = 'arn:aws:iot:%s:1:cert/%s' % (region_name, self.certificate_id) + self.certificate_pem = certificate_pem + self.status = status + + # TODO: must adjust + self.owner = '1' + self.transfer_data = {} + self.creation_date = time.time() + self.last_modified_date = self.creation_date + self.ca_certificate_id = None + + def to_dict(self): + return { + 'certificateArn': self.arn, + 'certificateId': self.certificate_id, + 'status': self.status, + 'creationDate': self.creation_date + } + + def to_description_dict(self): + """ + You might need keys below in some situation + - caCertificateId + - previousOwnedBy + """ + return { + 'certificateArn': self.arn, + 'certificateId': self.certificate_id, + 'status': self.status, + 'certificatePem': self.certificate_pem, + 'ownedBy': self.owner, + 'creationDate': self.creation_date, + 'lastModifiedDate': self.last_modified_date, + 'transferData': self.transfer_data + } + + +class FakePolicy(BaseModel): + def __init__(self, name, document, region_name, default_version_id='1'): + self.name = name + self.document = document + self.arn = 'arn:aws:iot:%s:1:policy/%s' % (region_name, name) + self.default_version_id = default_version_id + self.versions = [FakePolicyVersion(self.name, document, True, region_name)] + + def to_get_dict(self): + return { + 'policyName': self.name, + 'policyArn': self.arn, + 'policyDocument': self.document, + 'defaultVersionId': self.default_version_id + } + + def to_dict_at_creation(self): + return { + 'policyName': self.name, + 'policyArn': self.arn, + 'policyDocument': self.document, + 'policyVersionId': self.default_version_id + } + + def to_dict(self): + return { + 'policyName': self.name, + 'policyArn': self.arn, + } + + +class FakePolicyVersion(object): + + def __init__(self, + policy_name, + document, + is_default, + region_name): + self.name = policy_name + self.arn = 'arn:aws:iot:%s:1:policy/%s' % (region_name, policy_name) + self.document = document or {} + self.is_default = is_default + self.version_id = '1' + + self.create_datetime = time.mktime(datetime(2015, 1, 1).timetuple()) + self.last_modified_datetime = time.mktime(datetime(2015, 1, 2).timetuple()) + + def to_get_dict(self): + return { + 'policyName': self.name, + 'policyArn': self.arn, + 'policyDocument': self.document, + 'policyVersionId': self.version_id, + 'isDefaultVersion': self.is_default, + 'creationDate': self.create_datetime, + 'lastModifiedDate': self.last_modified_datetime, + 'generationId': self.version_id + } + + def to_dict_at_creation(self): + return { + 'policyArn': self.arn, + 'policyDocument': self.document, + 'policyVersionId': self.version_id, + 'isDefaultVersion': self.is_default + } + + def to_dict(self): + return { + 'versionId': self.version_id, + 'isDefaultVersion': self.is_default, + 'createDate': self.create_datetime, + } + + +class FakeJob(BaseModel): + JOB_ID_REGEX_PATTERN = "[a-zA-Z0-9_-]" + JOB_ID_REGEX = re.compile(JOB_ID_REGEX_PATTERN) + + def __init__(self, job_id, targets, document_source, document, description, presigned_url_config, target_selection, + job_executions_rollout_config, document_parameters, region_name): + if not self._job_id_matcher(self.JOB_ID_REGEX, job_id): + raise InvalidRequestException() + + self.region_name = region_name + self.job_id = job_id + self.job_arn = 'arn:aws:iot:%s:1:job/%s' % (self.region_name, job_id) + self.targets = targets + self.document_source = document_source + self.document = document + self.description = description + self.presigned_url_config = presigned_url_config + self.target_selection = target_selection + self.job_executions_rollout_config = job_executions_rollout_config + self.status = None # IN_PROGRESS | CANCELED | COMPLETED + self.comment = None + self.created_at = time.mktime(datetime(2015, 1, 1).timetuple()) + self.last_updated_at = time.mktime(datetime(2015, 1, 1).timetuple()) + self.completed_at = None + self.job_process_details = { + 'processingTargets': targets, + 'numberOfQueuedThings': 1, + 'numberOfCanceledThings': 0, + 'numberOfSucceededThings': 0, + 'numberOfFailedThings': 0, + 'numberOfRejectedThings': 0, + 'numberOfInProgressThings': 0, + 'numberOfRemovedThings': 0 + } + self.document_parameters = document_parameters + + def to_dict(self): + + obj = { + 'jobArn': self.job_arn, + 'jobId': self.job_id, + 'targets': self.targets, + 'description': self.description, + 'presignedUrlConfig': self.presigned_url_config, + 'targetSelection': self.target_selection, + 'jobExecutionsRolloutConfig': self.job_executions_rollout_config, + 'status': self.status, + 'comment': self.comment, + 'createdAt': self.created_at, + 'lastUpdatedAt': self.last_updated_at, + 'completedAt': self.completedAt, + 'jobProcessDetails': self.job_process_details, + 'documentParameters': self.document_parameters, + 'document': self.document, + 'documentSource': self.document_source + } + + return obj + + def _job_id_matcher(self, regex, argument): + regex_match = regex.match(argument) + length_match = len(argument) <= 64 + return regex_match and length_match + + +class FakeJobExecution(BaseModel): + + def __init__(self, job_id, thing_arn, status='QUEUED', force_canceled=False, status_details_map={}): + self.job_id = job_id + self.status = status # IN_PROGRESS | CANCELED | COMPLETED + self.force_canceled = force_canceled + self.status_details_map = status_details_map + self.thing_arn = thing_arn + self.queued_at = time.mktime(datetime(2015, 1, 1).timetuple()) + self.started_at = time.mktime(datetime(2015, 1, 1).timetuple()) + self.last_updated_at = time.mktime(datetime(2015, 1, 1).timetuple()) + self.execution_number = 123 + self.version_number = 123 + self.approximate_seconds_before_time_out = 123 + + def to_dict(self): + obj = { + 'jobId': self.job_id, + 'status': self.status, + 'forceCancel': self.force_canceled, + 'statusDetails': {'detailsMap': self.status_details_map}, + 'thing_arn': self.thing_arn, + 'queuedAt': self.queued_at, + 'startedAt': self.started_at, + 'lastUpdatedAt': self.last_updated_at, + 'executionNumber': self.execution_number, + 'versionNumber': self.version_number, + 'approximateSecondsBeforeTimedOut': self.approximate_seconds_before_time_out + } + + return obj + + +class IoTBackend(BaseBackend): + def __init__(self, region_name=None): + super(IoTBackend, self).__init__() + self.region_name = region_name + self.things = OrderedDict() + self.jobs = OrderedDict() + self.job_executions = OrderedDict() + self.thing_types = OrderedDict() + self.thing_groups = OrderedDict() + self.certificates = OrderedDict() + self.policies = OrderedDict() + self.principal_policies = OrderedDict() + self.principal_things = OrderedDict() + + def reset(self): + region_name = self.region_name + self.__dict__ = {} + self.__init__(region_name) + + def create_thing(self, thing_name, thing_type_name, attribute_payload): + thing_types = self.list_thing_types() + thing_type = None + if thing_type_name: + filtered_thing_types = [_ for _ in thing_types if _.thing_type_name == thing_type_name] + if len(filtered_thing_types) == 0: + raise ResourceNotFoundException() + thing_type = filtered_thing_types[0] + if attribute_payload is None: + attributes = {} + elif 'attributes' not in attribute_payload: + attributes = {} + else: + attributes = attribute_payload['attributes'] + thing = FakeThing(thing_name, thing_type, attributes, self.region_name) + self.things[thing.arn] = thing + return thing.thing_name, thing.arn + + def create_thing_type(self, thing_type_name, thing_type_properties): + if thing_type_properties is None: + thing_type_properties = {} + thing_type = FakeThingType(thing_type_name, thing_type_properties, self.region_name) + self.thing_types[thing_type.arn] = thing_type + return thing_type.thing_type_name, thing_type.arn + + def list_thing_types(self, thing_type_name=None): + if thing_type_name: + # It's weird but thing_type_name is filtered by forward match, not complete match + return [_ for _ in self.thing_types.values() if _.thing_type_name.startswith(thing_type_name)] + return self.thing_types.values() + + def list_things(self, attribute_name, attribute_value, thing_type_name, max_results, token): + all_things = [_.to_dict() for _ in self.things.values()] + if attribute_name is not None and thing_type_name is not None: + filtered_things = list(filter(lambda elem: + attribute_name in elem["attributes"] and + elem["attributes"][attribute_name] == attribute_value and + "thingTypeName" in elem and + elem["thingTypeName"] == thing_type_name, all_things)) + elif attribute_name is not None and thing_type_name is None: + filtered_things = list(filter(lambda elem: + attribute_name in elem["attributes"] and + elem["attributes"][attribute_name] == attribute_value, all_things)) + elif attribute_name is None and thing_type_name is not None: + filtered_things = list( + filter(lambda elem: "thingTypeName" in elem and elem["thingTypeName"] == thing_type_name, all_things)) + else: + filtered_things = all_things + + if token is None: + things = filtered_things[0:max_results] + next_token = str(max_results) if len(filtered_things) > max_results else None + else: + token = int(token) + things = filtered_things[token:token + max_results] + next_token = str(token + max_results) if len(filtered_things) > token + max_results else None + + return things, next_token + + def describe_thing(self, thing_name): + things = [_ for _ in self.things.values() if _.thing_name == thing_name] + if len(things) == 0: + raise ResourceNotFoundException() + return things[0] + + def describe_thing_type(self, thing_type_name): + thing_types = [_ for _ in self.thing_types.values() if _.thing_type_name == thing_type_name] + if len(thing_types) == 0: + raise ResourceNotFoundException() + return thing_types[0] + + def delete_thing(self, thing_name, expected_version): + # TODO: handle expected_version + + # can raise ResourceNotFoundError + thing = self.describe_thing(thing_name) + del self.things[thing.arn] + + def delete_thing_type(self, thing_type_name): + # can raise ResourceNotFoundError + thing_type = self.describe_thing_type(thing_type_name) + del self.thing_types[thing_type.arn] + + def update_thing(self, thing_name, thing_type_name, attribute_payload, expected_version, remove_thing_type): + # if attributes payload = {}, nothing + thing = self.describe_thing(thing_name) + thing_type = None + + if remove_thing_type and thing_type_name: + raise InvalidRequestException() + + # thing_type + if thing_type_name: + thing_types = self.list_thing_types() + filtered_thing_types = [_ for _ in thing_types if _.thing_type_name == thing_type_name] + if len(filtered_thing_types) == 0: + raise ResourceNotFoundException() + thing_type = filtered_thing_types[0] + thing.thing_type = thing_type + + if remove_thing_type: + thing.thing_type = None + + # attribute + if attribute_payload is not None and 'attributes' in attribute_payload: + do_merge = attribute_payload.get('merge', False) + attributes = attribute_payload['attributes'] + if not do_merge: + thing.attributes = attributes + else: + thing.attributes.update(attributes) + + def _random_string(self): + n = 20 + random_str = ''.join([random.choice(string.ascii_letters + string.digits) for i in range(n)]) + return random_str + + def create_keys_and_certificate(self, set_as_active): + # implement here + # caCertificate can be blank + key_pair = { + 'PublicKey': self._random_string(), + 'PrivateKey': self._random_string() + } + certificate_pem = self._random_string() + status = 'ACTIVE' if set_as_active else 'INACTIVE' + certificate = FakeCertificate(certificate_pem, status, self.region_name) + self.certificates[certificate.certificate_id] = certificate + return certificate, key_pair + + def delete_certificate(self, certificate_id): + self.describe_certificate(certificate_id) + del self.certificates[certificate_id] + + def describe_certificate(self, certificate_id): + certs = [_ for _ in self.certificates.values() if _.certificate_id == certificate_id] + if len(certs) == 0: + raise ResourceNotFoundException() + return certs[0] + + def list_certificates(self): + return self.certificates.values() + + def update_certificate(self, certificate_id, new_status): + cert = self.describe_certificate(certificate_id) + # TODO: validate new_status + cert.status = new_status + + def create_policy(self, policy_name, policy_document): + policy = FakePolicy(policy_name, policy_document, self.region_name) + self.policies[policy.name] = policy + return policy + + def attach_policy(self, policy_name, target): + principal = self._get_principal(target) + policy = self.get_policy(policy_name) + k = (target, policy_name) + if k in self.principal_policies: + return + self.principal_policies[k] = (principal, policy) + + def detach_policy(self, policy_name, target): + # this may raises ResourceNotFoundException + self._get_principal(target) + self.get_policy(policy_name) + + k = (target, policy_name) + if k not in self.principal_policies: + raise ResourceNotFoundException() + del self.principal_policies[k] + + def list_attached_policies(self, target): + policies = [v[1] for k, v in self.principal_policies.items() if k[0] == target] + return policies + + def list_policies(self): + policies = self.policies.values() + return policies + + def get_policy(self, policy_name): + policies = [_ for _ in self.policies.values() if _.name == policy_name] + if len(policies) == 0: + raise ResourceNotFoundException() + return policies[0] + + def delete_policy(self, policy_name): + policy = self.get_policy(policy_name) + del self.policies[policy.name] + + def create_policy_version(self, policy_name, policy_document, set_as_default): + policy = self.get_policy(policy_name) + if not policy: + raise ResourceNotFoundException() + version = FakePolicyVersion(policy_name, policy_document, set_as_default, self.region_name) + policy.versions.append(version) + version.version_id = '{0}'.format(len(policy.versions)) + if set_as_default: + self.set_default_policy_version(policy_name, version.version_id) + return version + + def set_default_policy_version(self, policy_name, version_id): + policy = self.get_policy(policy_name) + if not policy: + raise ResourceNotFoundException() + for version in policy.versions: + if version.version_id == version_id: + version.is_default = True + policy.default_version_id = version.version_id + policy.document = version.document + else: + version.is_default = False + + def get_policy_version(self, policy_name, version_id): + policy = self.get_policy(policy_name) + if not policy: + raise ResourceNotFoundException() + for version in policy.versions: + if version.version_id == version_id: + return version + raise ResourceNotFoundException() + + def list_policy_versions(self, policy_name): + policy = self.get_policy(policy_name) + if not policy: + raise ResourceNotFoundException() + return policy.versions + + def delete_policy_version(self, policy_name, version_id): + policy = self.get_policy(policy_name) + if not policy: + raise ResourceNotFoundException() + if version_id == policy.default_version_id: + raise InvalidRequestException( + "Cannot delete the default version of a policy") + for i, v in enumerate(policy.versions): + if v.version_id == version_id: + del policy.versions[i] + return + raise ResourceNotFoundException() + + def _get_principal(self, principal_arn): + """ + raise ResourceNotFoundException + """ + if ':cert/' in principal_arn: + certs = [_ for _ in self.certificates.values() if _.arn == principal_arn] + if len(certs) == 0: + raise ResourceNotFoundException() + principal = certs[0] + return principal + else: + # TODO: search for cognito_ids + pass + raise ResourceNotFoundException() + + def attach_principal_policy(self, policy_name, principal_arn): + principal = self._get_principal(principal_arn) + policy = self.get_policy(policy_name) + k = (principal_arn, policy_name) + if k in self.principal_policies: + return + self.principal_policies[k] = (principal, policy) + + def detach_principal_policy(self, policy_name, principal_arn): + # this may raises ResourceNotFoundException + self._get_principal(principal_arn) + self.get_policy(policy_name) + + k = (principal_arn, policy_name) + if k not in self.principal_policies: + raise ResourceNotFoundException() + del self.principal_policies[k] + + def list_principal_policies(self, principal_arn): + policies = [v[1] for k, v in self.principal_policies.items() if k[0] == principal_arn] + return policies + + def list_policy_principals(self, policy_name): + principals = [k[0] for k, v in self.principal_policies.items() if k[1] == policy_name] + return principals + + def attach_thing_principal(self, thing_name, principal_arn): + principal = self._get_principal(principal_arn) + thing = self.describe_thing(thing_name) + k = (principal_arn, thing_name) + if k in self.principal_things: + return + self.principal_things[k] = (principal, thing) + + def detach_thing_principal(self, thing_name, principal_arn): + # this may raises ResourceNotFoundException + self._get_principal(principal_arn) + self.describe_thing(thing_name) + + k = (principal_arn, thing_name) + if k not in self.principal_things: + raise ResourceNotFoundException() + del self.principal_things[k] + + def list_principal_things(self, principal_arn): + thing_names = [k[0] for k, v in self.principal_things.items() if k[0] == principal_arn] + return thing_names + + def list_thing_principals(self, thing_name): + principals = [k[0] for k, v in self.principal_things.items() if k[1] == thing_name] + return principals + + def describe_thing_group(self, thing_group_name): + thing_groups = [_ for _ in self.thing_groups.values() if _.thing_group_name == thing_group_name] + if len(thing_groups) == 0: + raise ResourceNotFoundException() + return thing_groups[0] + + def create_thing_group(self, thing_group_name, parent_group_name, thing_group_properties): + thing_group = FakeThingGroup(thing_group_name, parent_group_name, thing_group_properties, self.region_name) + self.thing_groups[thing_group.arn] = thing_group + return thing_group.thing_group_name, thing_group.arn, thing_group.thing_group_id + + def delete_thing_group(self, thing_group_name, expected_version): + thing_group = self.describe_thing_group(thing_group_name) + del self.thing_groups[thing_group.arn] + + def list_thing_groups(self, parent_group, name_prefix_filter, recursive): + thing_groups = self.thing_groups.values() + return thing_groups + + def update_thing_group(self, thing_group_name, thing_group_properties, expected_version): + thing_group = self.describe_thing_group(thing_group_name) + if expected_version and expected_version != thing_group.version: + raise VersionConflictException(thing_group_name) + attribute_payload = thing_group_properties.get('attributePayload', None) + if attribute_payload is not None and 'attributes' in attribute_payload: + do_merge = attribute_payload.get('merge', False) + attributes = attribute_payload['attributes'] + if not do_merge: + thing_group.thing_group_properties['attributePayload']['attributes'] = attributes + else: + thing_group.thing_group_properties['attributePayload']['attributes'].update(attributes) + elif attribute_payload is not None and 'attributes' not in attribute_payload: + thing_group.attributes = {} + thing_group.version = thing_group.version + 1 + return thing_group.version + + def _identify_thing_group(self, thing_group_name, thing_group_arn): + # identify thing group + if thing_group_name is None and thing_group_arn is None: + raise InvalidRequestException( + ' Both thingGroupArn and thingGroupName are empty. Need to specify at least one of them' + ) + if thing_group_name is not None: + thing_group = self.describe_thing_group(thing_group_name) + if thing_group_arn and thing_group.arn != thing_group_arn: + raise InvalidRequestException( + 'ThingGroupName thingGroupArn does not match specified thingGroupName in request' + ) + elif thing_group_arn is not None: + if thing_group_arn not in self.thing_groups: + raise InvalidRequestException() + thing_group = self.thing_groups[thing_group_arn] + return thing_group + + def _identify_thing(self, thing_name, thing_arn): + # identify thing + if thing_name is None and thing_arn is None: + raise InvalidRequestException( + 'Both thingArn and thingName are empty. Need to specify at least one of them' + ) + if thing_name is not None: + thing = self.describe_thing(thing_name) + if thing_arn and thing.arn != thing_arn: + raise InvalidRequestException( + 'ThingName thingArn does not match specified thingName in request' + ) + elif thing_arn is not None: + if thing_arn not in self.things: + raise InvalidRequestException() + thing = self.things[thing_arn] + return thing + + def add_thing_to_thing_group(self, thing_group_name, thing_group_arn, thing_name, thing_arn): + thing_group = self._identify_thing_group(thing_group_name, thing_group_arn) + thing = self._identify_thing(thing_name, thing_arn) + if thing.arn in thing_group.things: + # aws ignores duplicate registration + return + thing_group.things[thing.arn] = thing + + def remove_thing_from_thing_group(self, thing_group_name, thing_group_arn, thing_name, thing_arn): + thing_group = self._identify_thing_group(thing_group_name, thing_group_arn) + thing = self._identify_thing(thing_name, thing_arn) + if thing.arn not in thing_group.things: + # aws ignores non-registered thing + return + del thing_group.things[thing.arn] + + def list_things_in_thing_group(self, thing_group_name, recursive): + thing_group = self.describe_thing_group(thing_group_name) + return thing_group.things.values() + + def list_thing_groups_for_thing(self, thing_name): + thing = self.describe_thing(thing_name) + all_thing_groups = self.list_thing_groups(None, None, None) + ret = [] + for thing_group in all_thing_groups: + if thing.arn in thing_group.things: + ret.append({ + 'groupName': thing_group.thing_group_name, + 'groupArn': thing_group.arn + }) + return ret + + def update_thing_groups_for_thing(self, thing_name, thing_groups_to_add, thing_groups_to_remove): + thing = self.describe_thing(thing_name) + for thing_group_name in thing_groups_to_add: + thing_group = self.describe_thing_group(thing_group_name) + self.add_thing_to_thing_group( + thing_group.thing_group_name, None, + thing.thing_name, None + ) + for thing_group_name in thing_groups_to_remove: + thing_group = self.describe_thing_group(thing_group_name) + self.remove_thing_from_thing_group( + thing_group.thing_group_name, None, + thing.thing_name, None + ) + + def create_job(self, job_id, targets, document_source, document, description, presigned_url_config, + target_selection, job_executions_rollout_config, document_parameters): + job = FakeJob(job_id, targets, document_source, document, description, presigned_url_config, target_selection, + job_executions_rollout_config, document_parameters, self.region_name) + self.jobs[job_id] = job + + for thing_arn in targets: + thing_name = thing_arn.split(':')[-1] + job_execution = FakeJobExecution(job_id, thing_arn) + self.job_executions[(job_id, thing_name)] = job_execution + return job.job_arn, job_id, description + + def describe_job(self, job_id): + return self.jobs[job_id] + + def get_job_document(self, job_id): + return self.jobs[job_id] + + def describe_job_execution(self, job_id, thing_name, execution_number): + # TODO filter with execution number + return self.job_executions[(job_id, thing_name)] + + def list_job_executions_for_job(self, job_id, status, max_results, next_token): + job_executions = [self.job_executions[je] for je in self.job_executions if je[0] == job_id] + # TODO: implement filters + return job_executions, next_token + + +available_regions = boto3.session.Session().get_available_regions("iot") +iot_backends = {region: IoTBackend(region) for region in available_regions} diff --git a/moto/iot/responses.py b/moto/iot/responses.py index 3ef5bc93e..14302cc2f 100644 --- a/moto/iot/responses.py +++ b/moto/iot/responses.py @@ -1,497 +1,507 @@ -from __future__ import unicode_literals - -import json -from six.moves.urllib.parse import unquote - -from moto.core.responses import BaseResponse -from .models import iot_backends - - -class IoTResponse(BaseResponse): - SERVICE_NAME = 'iot' - - @property - def iot_backend(self): - return iot_backends[self.region] - - def create_thing(self): - thing_name = self._get_param("thingName") - thing_type_name = self._get_param("thingTypeName") - attribute_payload = self._get_param("attributePayload") - thing_name, thing_arn = self.iot_backend.create_thing( - thing_name=thing_name, - thing_type_name=thing_type_name, - attribute_payload=attribute_payload, - ) - return json.dumps(dict(thingName=thing_name, thingArn=thing_arn)) - - def create_thing_type(self): - thing_type_name = self._get_param("thingTypeName") - thing_type_properties = self._get_param("thingTypeProperties") - thing_type_name, thing_type_arn = self.iot_backend.create_thing_type( - thing_type_name=thing_type_name, - thing_type_properties=thing_type_properties, - ) - return json.dumps(dict(thingTypeName=thing_type_name, thingTypeArn=thing_type_arn)) - - def list_thing_types(self): - previous_next_token = self._get_param("nextToken") - max_results = self._get_int_param("maxResults", 50) # not the default, but makes testing easier - thing_type_name = self._get_param("thingTypeName") - thing_types = self.iot_backend.list_thing_types( - thing_type_name=thing_type_name - ) - - thing_types = [_.to_dict() for _ in thing_types] - if previous_next_token is None: - result = thing_types[0:max_results] - next_token = str(max_results) if len(thing_types) > max_results else None - else: - token = int(previous_next_token) - result = thing_types[token:token + max_results] - next_token = str(token + max_results) if len(thing_types) > token + max_results else None - - return json.dumps(dict(thingTypes=result, nextToken=next_token)) - - def list_things(self): - previous_next_token = self._get_param("nextToken") - max_results = self._get_int_param("maxResults", 50) # not the default, but makes testing easier - attribute_name = self._get_param("attributeName") - attribute_value = self._get_param("attributeValue") - thing_type_name = self._get_param("thingTypeName") - things, next_token = self.iot_backend.list_things( - attribute_name=attribute_name, - attribute_value=attribute_value, - thing_type_name=thing_type_name, - max_results=max_results, - token=previous_next_token - ) - - return json.dumps(dict(things=things, nextToken=next_token)) - - def describe_thing(self): - thing_name = self._get_param("thingName") - thing = self.iot_backend.describe_thing( - thing_name=thing_name, - ) - return json.dumps(thing.to_dict(include_default_client_id=True)) - - def describe_thing_type(self): - thing_type_name = self._get_param("thingTypeName") - thing_type = self.iot_backend.describe_thing_type( - thing_type_name=thing_type_name, - ) - return json.dumps(thing_type.to_dict()) - - def delete_thing(self): - thing_name = self._get_param("thingName") - expected_version = self._get_param("expectedVersion") - self.iot_backend.delete_thing( - thing_name=thing_name, - expected_version=expected_version, - ) - return json.dumps(dict()) - - def delete_thing_type(self): - thing_type_name = self._get_param("thingTypeName") - self.iot_backend.delete_thing_type( - thing_type_name=thing_type_name, - ) - return json.dumps(dict()) - - def update_thing(self): - thing_name = self._get_param("thingName") - thing_type_name = self._get_param("thingTypeName") - attribute_payload = self._get_param("attributePayload") - expected_version = self._get_param("expectedVersion") - remove_thing_type = self._get_param("removeThingType") - self.iot_backend.update_thing( - thing_name=thing_name, - thing_type_name=thing_type_name, - attribute_payload=attribute_payload, - expected_version=expected_version, - remove_thing_type=remove_thing_type, - ) - return json.dumps(dict()) - - def create_job(self): - job_arn, job_id, description = self.iot_backend.create_job( - job_id=self._get_param("jobId"), - targets=self._get_param("targets"), - description=self._get_param("description"), - document_source=self._get_param("documentSource"), - document=self._get_param("document"), - presigned_url_config=self._get_param("presignedUrlConfig"), - target_selection=self._get_param("targetSelection"), - job_executions_rollout_config=self._get_param("jobExecutionsRolloutConfig"), - document_parameters=self._get_param("documentParameters") - ) - - return json.dumps(dict(jobArn=job_arn, jobId=job_id, description=description)) - - def describe_job(self): - job = self.iot_backend.describe_job(job_id=self._get_param("jobId")) - return json.dumps(dict( - documentSource=job.document_source, - job=dict( - comment=job.comment, - completedAt=job.completed_at, - createdAt=job.created_at, - description=job.description, - documentParameters=job.document_parameters, - jobArn=job.job_arn, - jobExecutionsRolloutConfig=job.job_executions_rollout_config, - jobId=job.job_id, - jobProcessDetails=job.job_process_details, - lastUpdatedAt=job.last_updated_at, - presignedUrlConfig=job.presigned_url_config, - status=job.status, - targets=job.targets, - targetSelection=job.target_selection - ))) - - def get_job_document(self): - job = self.iot_backend.get_job_document(job_id=self._get_param("jobId")) - - if job.document is not None: - return json.dumps({'document': job.document}) - else: - # job.document_source is not None: - # TODO: needs to be implemented to get document_source's content from S3 - return json.dumps({'document': ''}) - - def create_keys_and_certificate(self): - set_as_active = self._get_bool_param("setAsActive") - cert, key_pair = self.iot_backend.create_keys_and_certificate( - set_as_active=set_as_active, - ) - return json.dumps(dict( - certificateArn=cert.arn, - certificateId=cert.certificate_id, - certificatePem=cert.certificate_pem, - keyPair=key_pair - )) - - def delete_certificate(self): - certificate_id = self._get_param("certificateId") - self.iot_backend.delete_certificate( - certificate_id=certificate_id, - ) - return json.dumps(dict()) - - def describe_certificate(self): - certificate_id = self._get_param("certificateId") - certificate = self.iot_backend.describe_certificate( - certificate_id=certificate_id, - ) - return json.dumps(dict(certificateDescription=certificate.to_description_dict())) - - def list_certificates(self): - # page_size = self._get_int_param("pageSize") - # marker = self._get_param("marker") - # ascending_order = self._get_param("ascendingOrder") - certificates = self.iot_backend.list_certificates() - # TODO: implement pagination in the future - return json.dumps(dict(certificates=[_.to_dict() for _ in certificates])) - - def update_certificate(self): - certificate_id = self._get_param("certificateId") - new_status = self._get_param("newStatus") - self.iot_backend.update_certificate( - certificate_id=certificate_id, - new_status=new_status, - ) - return json.dumps(dict()) - - def create_policy(self): - policy_name = self._get_param("policyName") - policy_document = self._get_param("policyDocument") - policy = self.iot_backend.create_policy( - policy_name=policy_name, - policy_document=policy_document, - ) - return json.dumps(policy.to_dict_at_creation()) - - def list_policies(self): - # marker = self._get_param("marker") - # page_size = self._get_int_param("pageSize") - # ascending_order = self._get_param("ascendingOrder") - policies = self.iot_backend.list_policies() - - # TODO: implement pagination in the future - return json.dumps(dict(policies=[_.to_dict() for _ in policies])) - - def get_policy(self): - policy_name = self._get_param("policyName") - policy = self.iot_backend.get_policy( - policy_name=policy_name, - ) - return json.dumps(policy.to_get_dict()) - - def delete_policy(self): - policy_name = self._get_param("policyName") - self.iot_backend.delete_policy( - policy_name=policy_name, - ) - return json.dumps(dict()) - - def create_policy_version(self): - policy_name = self._get_param('policyName') - policy_document = self._get_param('policyDocument') - set_as_default = self._get_bool_param('setAsDefault') - policy_version = self.iot_backend.create_policy_version(policy_name, policy_document, set_as_default) - - return json.dumps(dict(policy_version.to_dict_at_creation())) - - def set_default_policy_version(self): - policy_name = self._get_param('policyName') - version_id = self._get_param('policyVersionId') - self.iot_backend.set_default_policy_version(policy_name, version_id) - - return json.dumps(dict()) - - def get_policy_version(self): - policy_name = self._get_param('policyName') - version_id = self._get_param('policyVersionId') - policy_version = self.iot_backend.get_policy_version(policy_name, version_id) - return json.dumps(dict(policy_version.to_get_dict())) - - def list_policy_versions(self): - policy_name = self._get_param('policyName') - policiy_versions = self.iot_backend.list_policy_versions(policy_name=policy_name) - - return json.dumps(dict(policyVersions=[_.to_dict() for _ in policiy_versions])) - - def delete_policy_version(self): - policy_name = self._get_param('policyName') - version_id = self._get_param('policyVersionId') - self.iot_backend.delete_policy_version(policy_name, version_id) - - return json.dumps(dict()) - - def attach_policy(self): - policy_name = self._get_param("policyName") - principal = self._get_param('target') - self.iot_backend.attach_policy( - policy_name=policy_name, - target=principal, - ) - return json.dumps(dict()) - - def detach_policy(self): - policy_name = self._get_param("policyName") - principal = self._get_param('target') - self.iot_backend.detach_policy( - policy_name=policy_name, - target=principal, - ) - return json.dumps(dict()) - - def list_attached_policies(self): - principal = unquote(self._get_param('target')) - # marker = self._get_param("marker") - # page_size = self._get_int_param("pageSize") - policies = self.iot_backend.list_attached_policies( - target=principal - ) - # TODO: implement pagination in the future - next_marker = None - return json.dumps(dict(policies=[_.to_dict() for _ in policies], nextMarker=next_marker)) - - def attach_principal_policy(self): - policy_name = self._get_param("policyName") - principal = self.headers.get('x-amzn-iot-principal') - self.iot_backend.attach_principal_policy( - policy_name=policy_name, - principal_arn=principal, - ) - return json.dumps(dict()) - - def detach_principal_policy(self): - policy_name = self._get_param("policyName") - principal = self.headers.get('x-amzn-iot-principal') - self.iot_backend.detach_principal_policy( - policy_name=policy_name, - principal_arn=principal, - ) - return json.dumps(dict()) - - def list_principal_policies(self): - principal = self.headers.get('x-amzn-iot-principal') - # marker = self._get_param("marker") - # page_size = self._get_int_param("pageSize") - # ascending_order = self._get_param("ascendingOrder") - policies = self.iot_backend.list_principal_policies( - principal_arn=principal - ) - # TODO: implement pagination in the future - next_marker = None - return json.dumps(dict(policies=[_.to_dict() for _ in policies], nextMarker=next_marker)) - - def list_policy_principals(self): - policy_name = self.headers.get('x-amzn-iot-policy') - # marker = self._get_param("marker") - # page_size = self._get_int_param("pageSize") - # ascending_order = self._get_param("ascendingOrder") - principals = self.iot_backend.list_policy_principals( - policy_name=policy_name, - ) - # TODO: implement pagination in the future - next_marker = None - return json.dumps(dict(principals=principals, nextMarker=next_marker)) - - def attach_thing_principal(self): - thing_name = self._get_param("thingName") - principal = self.headers.get('x-amzn-principal') - self.iot_backend.attach_thing_principal( - thing_name=thing_name, - principal_arn=principal, - ) - return json.dumps(dict()) - - def detach_thing_principal(self): - thing_name = self._get_param("thingName") - principal = self.headers.get('x-amzn-principal') - self.iot_backend.detach_thing_principal( - thing_name=thing_name, - principal_arn=principal, - ) - return json.dumps(dict()) - - def list_principal_things(self): - next_token = self._get_param("nextToken") - # max_results = self._get_int_param("maxResults") - principal = self.headers.get('x-amzn-principal') - things = self.iot_backend.list_principal_things( - principal_arn=principal, - ) - # TODO: implement pagination in the future - next_token = None - return json.dumps(dict(things=things, nextToken=next_token)) - - def list_thing_principals(self): - thing_name = self._get_param("thingName") - principals = self.iot_backend.list_thing_principals( - thing_name=thing_name, - ) - return json.dumps(dict(principals=principals)) - - def describe_thing_group(self): - thing_group_name = self._get_param("thingGroupName") - thing_group = self.iot_backend.describe_thing_group( - thing_group_name=thing_group_name, - ) - return json.dumps(thing_group.to_dict()) - - def create_thing_group(self): - thing_group_name = self._get_param("thingGroupName") - parent_group_name = self._get_param("parentGroupName") - thing_group_properties = self._get_param("thingGroupProperties") - thing_group_name, thing_group_arn, thing_group_id = self.iot_backend.create_thing_group( - thing_group_name=thing_group_name, - parent_group_name=parent_group_name, - thing_group_properties=thing_group_properties, - ) - return json.dumps(dict( - thingGroupName=thing_group_name, - thingGroupArn=thing_group_arn, - thingGroupId=thing_group_id) - ) - - def delete_thing_group(self): - thing_group_name = self._get_param("thingGroupName") - expected_version = self._get_param("expectedVersion") - self.iot_backend.delete_thing_group( - thing_group_name=thing_group_name, - expected_version=expected_version, - ) - return json.dumps(dict()) - - def list_thing_groups(self): - # next_token = self._get_param("nextToken") - # max_results = self._get_int_param("maxResults") - parent_group = self._get_param("parentGroup") - name_prefix_filter = self._get_param("namePrefixFilter") - recursive = self._get_param("recursive") - thing_groups = self.iot_backend.list_thing_groups( - parent_group=parent_group, - name_prefix_filter=name_prefix_filter, - recursive=recursive, - ) - next_token = None - rets = [{'groupName': _.thing_group_name, 'groupArn': _.arn} for _ in thing_groups] - # TODO: implement pagination in the future - return json.dumps(dict(thingGroups=rets, nextToken=next_token)) - - def update_thing_group(self): - thing_group_name = self._get_param("thingGroupName") - thing_group_properties = self._get_param("thingGroupProperties") - expected_version = self._get_param("expectedVersion") - version = self.iot_backend.update_thing_group( - thing_group_name=thing_group_name, - thing_group_properties=thing_group_properties, - expected_version=expected_version, - ) - return json.dumps(dict(version=version)) - - def add_thing_to_thing_group(self): - thing_group_name = self._get_param("thingGroupName") - thing_group_arn = self._get_param("thingGroupArn") - thing_name = self._get_param("thingName") - thing_arn = self._get_param("thingArn") - self.iot_backend.add_thing_to_thing_group( - thing_group_name=thing_group_name, - thing_group_arn=thing_group_arn, - thing_name=thing_name, - thing_arn=thing_arn, - ) - return json.dumps(dict()) - - def remove_thing_from_thing_group(self): - thing_group_name = self._get_param("thingGroupName") - thing_group_arn = self._get_param("thingGroupArn") - thing_name = self._get_param("thingName") - thing_arn = self._get_param("thingArn") - self.iot_backend.remove_thing_from_thing_group( - thing_group_name=thing_group_name, - thing_group_arn=thing_group_arn, - thing_name=thing_name, - thing_arn=thing_arn, - ) - return json.dumps(dict()) - - def list_things_in_thing_group(self): - thing_group_name = self._get_param("thingGroupName") - recursive = self._get_param("recursive") - # next_token = self._get_param("nextToken") - # max_results = self._get_int_param("maxResults") - things = self.iot_backend.list_things_in_thing_group( - thing_group_name=thing_group_name, - recursive=recursive, - ) - next_token = None - thing_names = [_.thing_name for _ in things] - # TODO: implement pagination in the future - return json.dumps(dict(things=thing_names, nextToken=next_token)) - - def list_thing_groups_for_thing(self): - thing_name = self._get_param("thingName") - # next_token = self._get_param("nextToken") - # max_results = self._get_int_param("maxResults") - thing_groups = self.iot_backend.list_thing_groups_for_thing( - thing_name=thing_name - ) - next_token = None - # TODO: implement pagination in the future - return json.dumps(dict(thingGroups=thing_groups, nextToken=next_token)) - - def update_thing_groups_for_thing(self): - thing_name = self._get_param("thingName") - thing_groups_to_add = self._get_param("thingGroupsToAdd") or [] - thing_groups_to_remove = self._get_param("thingGroupsToRemove") or [] - self.iot_backend.update_thing_groups_for_thing( - thing_name=thing_name, - thing_groups_to_add=thing_groups_to_add, - thing_groups_to_remove=thing_groups_to_remove, - ) - return json.dumps(dict()) +from __future__ import unicode_literals + +import json +from six.moves.urllib.parse import unquote + +from moto.core.responses import BaseResponse +from .models import iot_backends + + +class IoTResponse(BaseResponse): + SERVICE_NAME = 'iot' + + @property + def iot_backend(self): + return iot_backends[self.region] + + def create_thing(self): + thing_name = self._get_param("thingName") + thing_type_name = self._get_param("thingTypeName") + attribute_payload = self._get_param("attributePayload") + thing_name, thing_arn = self.iot_backend.create_thing( + thing_name=thing_name, + thing_type_name=thing_type_name, + attribute_payload=attribute_payload, + ) + return json.dumps(dict(thingName=thing_name, thingArn=thing_arn)) + + def create_thing_type(self): + thing_type_name = self._get_param("thingTypeName") + thing_type_properties = self._get_param("thingTypeProperties") + thing_type_name, thing_type_arn = self.iot_backend.create_thing_type( + thing_type_name=thing_type_name, + thing_type_properties=thing_type_properties, + ) + return json.dumps(dict(thingTypeName=thing_type_name, thingTypeArn=thing_type_arn)) + + def list_thing_types(self): + previous_next_token = self._get_param("nextToken") + max_results = self._get_int_param("maxResults", 50) # not the default, but makes testing easier + thing_type_name = self._get_param("thingTypeName") + thing_types = self.iot_backend.list_thing_types( + thing_type_name=thing_type_name + ) + + thing_types = [_.to_dict() for _ in thing_types] + if previous_next_token is None: + result = thing_types[0:max_results] + next_token = str(max_results) if len(thing_types) > max_results else None + else: + token = int(previous_next_token) + result = thing_types[token:token + max_results] + next_token = str(token + max_results) if len(thing_types) > token + max_results else None + + return json.dumps(dict(thingTypes=result, nextToken=next_token)) + + def list_things(self): + previous_next_token = self._get_param("nextToken") + max_results = self._get_int_param("maxResults", 50) # not the default, but makes testing easier + attribute_name = self._get_param("attributeName") + attribute_value = self._get_param("attributeValue") + thing_type_name = self._get_param("thingTypeName") + things, next_token = self.iot_backend.list_things( + attribute_name=attribute_name, + attribute_value=attribute_value, + thing_type_name=thing_type_name, + max_results=max_results, + token=previous_next_token + ) + + return json.dumps(dict(things=things, nextToken=next_token)) + + def describe_thing(self): + thing_name = self._get_param("thingName") + thing = self.iot_backend.describe_thing( + thing_name=thing_name, + ) + return json.dumps(thing.to_dict(include_default_client_id=True)) + + def describe_thing_type(self): + thing_type_name = self._get_param("thingTypeName") + thing_type = self.iot_backend.describe_thing_type( + thing_type_name=thing_type_name, + ) + return json.dumps(thing_type.to_dict()) + + def delete_thing(self): + thing_name = self._get_param("thingName") + expected_version = self._get_param("expectedVersion") + self.iot_backend.delete_thing( + thing_name=thing_name, + expected_version=expected_version, + ) + return json.dumps(dict()) + + def delete_thing_type(self): + thing_type_name = self._get_param("thingTypeName") + self.iot_backend.delete_thing_type( + thing_type_name=thing_type_name, + ) + return json.dumps(dict()) + + def update_thing(self): + thing_name = self._get_param("thingName") + thing_type_name = self._get_param("thingTypeName") + attribute_payload = self._get_param("attributePayload") + expected_version = self._get_param("expectedVersion") + remove_thing_type = self._get_param("removeThingType") + self.iot_backend.update_thing( + thing_name=thing_name, + thing_type_name=thing_type_name, + attribute_payload=attribute_payload, + expected_version=expected_version, + remove_thing_type=remove_thing_type, + ) + return json.dumps(dict()) + + def create_job(self): + job_arn, job_id, description = self.iot_backend.create_job( + job_id=self._get_param("jobId"), + targets=self._get_param("targets"), + description=self._get_param("description"), + document_source=self._get_param("documentSource"), + document=self._get_param("document"), + presigned_url_config=self._get_param("presignedUrlConfig"), + target_selection=self._get_param("targetSelection"), + job_executions_rollout_config=self._get_param("jobExecutionsRolloutConfig"), + document_parameters=self._get_param("documentParameters") + ) + + return json.dumps(dict(jobArn=job_arn, jobId=job_id, description=description)) + + def describe_job(self): + job = self.iot_backend.describe_job(job_id=self._get_param("jobId")) + return json.dumps(dict( + documentSource=job.document_source, + job=dict( + comment=job.comment, + completedAt=job.completed_at, + createdAt=job.created_at, + description=job.description, + documentParameters=job.document_parameters, + jobArn=job.job_arn, + jobExecutionsRolloutConfig=job.job_executions_rollout_config, + jobId=job.job_id, + jobProcessDetails=job.job_process_details, + lastUpdatedAt=job.last_updated_at, + presignedUrlConfig=job.presigned_url_config, + status=job.status, + targets=job.targets, + targetSelection=job.target_selection + ))) + + def get_job_document(self): + job = self.iot_backend.get_job_document(job_id=self._get_param("jobId")) + + if job.document is not None: + return json.dumps({'document': job.document}) + else: + # job.document_source is not None: + # TODO: needs to be implemented to get document_source's content from S3 + return json.dumps({'document': ''}) + + def list_job_executions_for_job(self): + job_executions, next_token = self.iot_backend.list_job_executions_for_job(job_id=self._get_param("jobId"), + status=self._get_param("status"), + max_results=self._get_param( + "maxResults"), + next_token=self._get_param( + "nextToken")) + + return json.dumps(dict(executionSummaries=[_.to_dict() for _ in job_executions], nextToken=next_token)) + + def create_keys_and_certificate(self): + set_as_active = self._get_bool_param("setAsActive") + cert, key_pair = self.iot_backend.create_keys_and_certificate( + set_as_active=set_as_active, + ) + return json.dumps(dict( + certificateArn=cert.arn, + certificateId=cert.certificate_id, + certificatePem=cert.certificate_pem, + keyPair=key_pair + )) + + def delete_certificate(self): + certificate_id = self._get_param("certificateId") + self.iot_backend.delete_certificate( + certificate_id=certificate_id, + ) + return json.dumps(dict()) + + def describe_certificate(self): + certificate_id = self._get_param("certificateId") + certificate = self.iot_backend.describe_certificate( + certificate_id=certificate_id, + ) + return json.dumps(dict(certificateDescription=certificate.to_description_dict())) + + def list_certificates(self): + # page_size = self._get_int_param("pageSize") + # marker = self._get_param("marker") + # ascending_order = self._get_param("ascendingOrder") + certificates = self.iot_backend.list_certificates() + # TODO: implement pagination in the future + return json.dumps(dict(certificates=[_.to_dict() for _ in certificates])) + + def update_certificate(self): + certificate_id = self._get_param("certificateId") + new_status = self._get_param("newStatus") + self.iot_backend.update_certificate( + certificate_id=certificate_id, + new_status=new_status, + ) + return json.dumps(dict()) + + def create_policy(self): + policy_name = self._get_param("policyName") + policy_document = self._get_param("policyDocument") + policy = self.iot_backend.create_policy( + policy_name=policy_name, + policy_document=policy_document, + ) + return json.dumps(policy.to_dict_at_creation()) + + def list_policies(self): + # marker = self._get_param("marker") + # page_size = self._get_int_param("pageSize") + # ascending_order = self._get_param("ascendingOrder") + policies = self.iot_backend.list_policies() + + # TODO: implement pagination in the future + return json.dumps(dict(policies=[_.to_dict() for _ in policies])) + + def get_policy(self): + policy_name = self._get_param("policyName") + policy = self.iot_backend.get_policy( + policy_name=policy_name, + ) + return json.dumps(policy.to_get_dict()) + + def delete_policy(self): + policy_name = self._get_param("policyName") + self.iot_backend.delete_policy( + policy_name=policy_name, + ) + return json.dumps(dict()) + + def create_policy_version(self): + policy_name = self._get_param('policyName') + policy_document = self._get_param('policyDocument') + set_as_default = self._get_bool_param('setAsDefault') + policy_version = self.iot_backend.create_policy_version(policy_name, policy_document, set_as_default) + + return json.dumps(dict(policy_version.to_dict_at_creation())) + + def set_default_policy_version(self): + policy_name = self._get_param('policyName') + version_id = self._get_param('policyVersionId') + self.iot_backend.set_default_policy_version(policy_name, version_id) + + return json.dumps(dict()) + + def get_policy_version(self): + policy_name = self._get_param('policyName') + version_id = self._get_param('policyVersionId') + policy_version = self.iot_backend.get_policy_version(policy_name, version_id) + return json.dumps(dict(policy_version.to_get_dict())) + + def list_policy_versions(self): + policy_name = self._get_param('policyName') + policiy_versions = self.iot_backend.list_policy_versions(policy_name=policy_name) + + return json.dumps(dict(policyVersions=[_.to_dict() for _ in policiy_versions])) + + def delete_policy_version(self): + policy_name = self._get_param('policyName') + version_id = self._get_param('policyVersionId') + self.iot_backend.delete_policy_version(policy_name, version_id) + + return json.dumps(dict()) + + def attach_policy(self): + policy_name = self._get_param("policyName") + principal = self._get_param('target') + self.iot_backend.attach_policy( + policy_name=policy_name, + target=principal, + ) + return json.dumps(dict()) + + def detach_policy(self): + policy_name = self._get_param("policyName") + principal = self._get_param('target') + self.iot_backend.detach_policy( + policy_name=policy_name, + target=principal, + ) + return json.dumps(dict()) + + def list_attached_policies(self): + principal = unquote(self._get_param('target')) + # marker = self._get_param("marker") + # page_size = self._get_int_param("pageSize") + policies = self.iot_backend.list_attached_policies( + target=principal + ) + # TODO: implement pagination in the future + next_marker = None + return json.dumps(dict(policies=[_.to_dict() for _ in policies], nextMarker=next_marker)) + + def attach_principal_policy(self): + policy_name = self._get_param("policyName") + principal = self.headers.get('x-amzn-iot-principal') + self.iot_backend.attach_principal_policy( + policy_name=policy_name, + principal_arn=principal, + ) + return json.dumps(dict()) + + def detach_principal_policy(self): + policy_name = self._get_param("policyName") + principal = self.headers.get('x-amzn-iot-principal') + self.iot_backend.detach_principal_policy( + policy_name=policy_name, + principal_arn=principal, + ) + return json.dumps(dict()) + + def list_principal_policies(self): + principal = self.headers.get('x-amzn-iot-principal') + # marker = self._get_param("marker") + # page_size = self._get_int_param("pageSize") + # ascending_order = self._get_param("ascendingOrder") + policies = self.iot_backend.list_principal_policies( + principal_arn=principal + ) + # TODO: implement pagination in the future + next_marker = None + return json.dumps(dict(policies=[_.to_dict() for _ in policies], nextMarker=next_marker)) + + def list_policy_principals(self): + policy_name = self.headers.get('x-amzn-iot-policy') + # marker = self._get_param("marker") + # page_size = self._get_int_param("pageSize") + # ascending_order = self._get_param("ascendingOrder") + principals = self.iot_backend.list_policy_principals( + policy_name=policy_name, + ) + # TODO: implement pagination in the future + next_marker = None + return json.dumps(dict(principals=principals, nextMarker=next_marker)) + + def attach_thing_principal(self): + thing_name = self._get_param("thingName") + principal = self.headers.get('x-amzn-principal') + self.iot_backend.attach_thing_principal( + thing_name=thing_name, + principal_arn=principal, + ) + return json.dumps(dict()) + + def detach_thing_principal(self): + thing_name = self._get_param("thingName") + principal = self.headers.get('x-amzn-principal') + self.iot_backend.detach_thing_principal( + thing_name=thing_name, + principal_arn=principal, + ) + return json.dumps(dict()) + + def list_principal_things(self): + next_token = self._get_param("nextToken") + # max_results = self._get_int_param("maxResults") + principal = self.headers.get('x-amzn-principal') + things = self.iot_backend.list_principal_things( + principal_arn=principal, + ) + # TODO: implement pagination in the future + next_token = None + return json.dumps(dict(things=things, nextToken=next_token)) + + def list_thing_principals(self): + thing_name = self._get_param("thingName") + principals = self.iot_backend.list_thing_principals( + thing_name=thing_name, + ) + return json.dumps(dict(principals=principals)) + + def describe_thing_group(self): + thing_group_name = self._get_param("thingGroupName") + thing_group = self.iot_backend.describe_thing_group( + thing_group_name=thing_group_name, + ) + return json.dumps(thing_group.to_dict()) + + def create_thing_group(self): + thing_group_name = self._get_param("thingGroupName") + parent_group_name = self._get_param("parentGroupName") + thing_group_properties = self._get_param("thingGroupProperties") + thing_group_name, thing_group_arn, thing_group_id = self.iot_backend.create_thing_group( + thing_group_name=thing_group_name, + parent_group_name=parent_group_name, + thing_group_properties=thing_group_properties, + ) + return json.dumps(dict( + thingGroupName=thing_group_name, + thingGroupArn=thing_group_arn, + thingGroupId=thing_group_id) + ) + + def delete_thing_group(self): + thing_group_name = self._get_param("thingGroupName") + expected_version = self._get_param("expectedVersion") + self.iot_backend.delete_thing_group( + thing_group_name=thing_group_name, + expected_version=expected_version, + ) + return json.dumps(dict()) + + def list_thing_groups(self): + # next_token = self._get_param("nextToken") + # max_results = self._get_int_param("maxResults") + parent_group = self._get_param("parentGroup") + name_prefix_filter = self._get_param("namePrefixFilter") + recursive = self._get_param("recursive") + thing_groups = self.iot_backend.list_thing_groups( + parent_group=parent_group, + name_prefix_filter=name_prefix_filter, + recursive=recursive, + ) + next_token = None + rets = [{'groupName': _.thing_group_name, 'groupArn': _.arn} for _ in thing_groups] + # TODO: implement pagination in the future + return json.dumps(dict(thingGroups=rets, nextToken=next_token)) + + def update_thing_group(self): + thing_group_name = self._get_param("thingGroupName") + thing_group_properties = self._get_param("thingGroupProperties") + expected_version = self._get_param("expectedVersion") + version = self.iot_backend.update_thing_group( + thing_group_name=thing_group_name, + thing_group_properties=thing_group_properties, + expected_version=expected_version, + ) + return json.dumps(dict(version=version)) + + def add_thing_to_thing_group(self): + thing_group_name = self._get_param("thingGroupName") + thing_group_arn = self._get_param("thingGroupArn") + thing_name = self._get_param("thingName") + thing_arn = self._get_param("thingArn") + self.iot_backend.add_thing_to_thing_group( + thing_group_name=thing_group_name, + thing_group_arn=thing_group_arn, + thing_name=thing_name, + thing_arn=thing_arn, + ) + return json.dumps(dict()) + + def remove_thing_from_thing_group(self): + thing_group_name = self._get_param("thingGroupName") + thing_group_arn = self._get_param("thingGroupArn") + thing_name = self._get_param("thingName") + thing_arn = self._get_param("thingArn") + self.iot_backend.remove_thing_from_thing_group( + thing_group_name=thing_group_name, + thing_group_arn=thing_group_arn, + thing_name=thing_name, + thing_arn=thing_arn, + ) + return json.dumps(dict()) + + def list_things_in_thing_group(self): + thing_group_name = self._get_param("thingGroupName") + recursive = self._get_param("recursive") + # next_token = self._get_param("nextToken") + # max_results = self._get_int_param("maxResults") + things = self.iot_backend.list_things_in_thing_group( + thing_group_name=thing_group_name, + recursive=recursive, + ) + next_token = None + thing_names = [_.thing_name for _ in things] + # TODO: implement pagination in the future + return json.dumps(dict(things=thing_names, nextToken=next_token)) + + def list_thing_groups_for_thing(self): + thing_name = self._get_param("thingName") + # next_token = self._get_param("nextToken") + # max_results = self._get_int_param("maxResults") + thing_groups = self.iot_backend.list_thing_groups_for_thing( + thing_name=thing_name + ) + next_token = None + # TODO: implement pagination in the future + return json.dumps(dict(thingGroups=thing_groups, nextToken=next_token)) + + def update_thing_groups_for_thing(self): + thing_name = self._get_param("thingName") + thing_groups_to_add = self._get_param("thingGroupsToAdd") or [] + thing_groups_to_remove = self._get_param("thingGroupsToRemove") or [] + self.iot_backend.update_thing_groups_for_thing( + thing_name=thing_name, + thing_groups_to_add=thing_groups_to_add, + thing_groups_to_remove=thing_groups_to_remove, + ) + return json.dumps(dict()) diff --git a/tests/test_iot/test_iot.py b/tests/test_iot/test_iot.py index 92fb3dfd0..d5f277d1d 100644 --- a/tests/test_iot/test_iot.py +++ b/tests/test_iot/test_iot.py @@ -874,3 +874,42 @@ def test_get_job_document_with_document(): job_document = client.get_job_document(jobId=job_id) job_document.should.have.key('document').which.should.equal("{\"field\": \"value\"}") + +@mock_iot +def test_list_job_executions_for_job(): + client = boto3.client('iot', region_name='eu-west-1') + name = "my-thing" + job_id = "TestJob" + # thing + thing = client.create_thing(thingName=name) + thing.should.have.key('thingName').which.should.equal(name) + thing.should.have.key('thingArn') + + # job document + job_document = { + "field": "value" + } + + job = client.create_job( + jobId=job_id, + targets=[thing["thingArn"]], + document=json.dumps(job_document), + description="Description", + presignedUrlConfig={ + 'roleArn': 'arn:aws:iam::1:role/service-role/iot_job_role', + 'expiresInSec': 123 + }, + targetSelection="CONTINUOUS", + jobExecutionsRolloutConfig={ + 'maximumPerMinute': 10 + } + ) + + job.should.have.key('jobId').which.should.equal(job_id) + job.should.have.key('jobArn') + job.should.have.key('description') + + job_execution = client.list_job_executions_for_job(jobId=job_id) + job_execution.should.have.key('executionSummaries') + job_execution['executionSummaries'][0].should.have.key('jobId').which.should.equal(job_id) +