diff --git a/moto/iot/models.py b/moto/iot/models.py index 4399e8790..89d71dd14 100644 --- a/moto/iot/models.py +++ b/moto/iot/models.py @@ -1,961 +1,961 @@ -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 ( - CertificateStateException, - DeleteConflictException, - ResourceNotFoundException, - InvalidRequestException, - InvalidStateTransitionException, - 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, ca_certificate_pem=None): - 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 - self.ca_certificate_pem = ca_certificate_pem - if ca_certificate_pem: - m.update(str(uuid.uuid4()).encode('utf-8')) - self.ca_certificate_id = m.hexdigest() - - def to_dict(self): - return { - 'certificateArn': self.arn, - 'certificateId': self.certificate_id, - 'caCertificateId': self.ca_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.force = False - 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 = 'QUEUED' # IN_PROGRESS | CANCELED | COMPLETED - self.comment = None - self.reason_code = 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, - 'forceCanceled': self.force, - 'reasonCode': self.reason_code, - 'createdAt': self.created_at, - 'lastUpdatedAt': self.last_updated_at, - 'completedAt': self.completed_at, - '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_get_dict(self): - obj = { - 'jobId': self.job_id, - 'status': self.status, - 'forceCanceled': self.force_canceled, - 'statusDetails': {'detailsMap': self.status_details_map}, - 'thingArn': 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 - - def to_dict(self): - obj = { - 'jobId': self.job_id, - 'thingArn': self.thing_arn, - 'jobExecutionSummary': { - 'status': self.status, - 'queuedAt': self.queued_at, - 'startedAt': self.started_at, - 'lastUpdatedAt': self.last_updated_at, - 'executionNumber': self.execution_number, - } - } - - 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): - cert = self.describe_certificate(certificate_id) - if cert.status == 'ACTIVE': - raise CertificateStateException( - 'Certificate must be deactivated (not ACTIVE) before deletion.', certificate_id) - - certs = [k[0] for k, v in self.principal_things.items() - if self._get_principal(k[0]).certificate_id == certificate_id] - if len(certs) > 0: - raise DeleteConflictException( - 'Things must be detached before deletion (arn: %s)' % certs[0] - ) - - certs = [k[0] for k, v in self.principal_policies.items() - if self._get_principal(k[0]).certificate_id == certificate_id] - if len(certs) > 0: - raise DeleteConflictException( - 'Certificate policies must be detached before deletion (arn: %s)' % certs[0] - ) - - 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 register_certificate(self, certificate_pem, ca_certificate_pem, set_as_active, status): - certificate = FakeCertificate(certificate_pem, 'ACTIVE' if set_as_active else status, - self.region_name, ca_certificate_pem) - self.certificates[certificate.certificate_id] = certificate - return certificate - - 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): - - policies = [k[1] for k, v in self.principal_policies.items() if k[1] == policy_name] - if len(policies) > 0: - raise DeleteConflictException( - 'The policy cannot be deleted as the policy is attached to one or more principals (name=%s)' - % 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].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): - jobs = [_ for _ in self.jobs.values() if _.job_id == job_id] - if len(jobs) == 0: - raise ResourceNotFoundException() - return jobs[0] - - def delete_job(self, job_id, force): - job = self.jobs[job_id] - - if job.status == 'IN_PROGRESS' and force: - del self.jobs[job_id] - elif job.status != 'IN_PROGRESS': - del self.jobs[job_id] - else: - raise InvalidStateTransitionException() - - def cancel_job(self, job_id, reason_code, comment, force): - job = self.jobs[job_id] - - job.reason_code = reason_code if reason_code is not None else job.reason_code - job.comment = comment if comment is not None else job.comment - job.force = force if force is not None and force != job.force else job.force - job.status = 'CANCELED' - - if job.status == 'IN_PROGRESS' and force: - self.jobs[job_id] = job - elif job.status != 'IN_PROGRESS': - self.jobs[job_id] = job - else: - raise InvalidStateTransitionException() - - return job - - def get_job_document(self, job_id): - return self.jobs[job_id] - - def list_jobs(self, status, target_selection, max_results, token, thing_group_name, thing_group_id): - # TODO: implement filters - all_jobs = [_.to_dict() for _ in self.jobs.values()] - filtered_jobs = all_jobs - - if token is None: - jobs = filtered_jobs[0:max_results] - next_token = str(max_results) if len(filtered_jobs) > max_results else None - else: - token = int(token) - jobs = filtered_jobs[token:token + max_results] - next_token = str(token + max_results) if len(filtered_jobs) > token + max_results else None - - return jobs, next_token - - def describe_job_execution(self, job_id, thing_name, execution_number): - try: - job_execution = self.job_executions[(job_id, thing_name)] - except KeyError: - raise ResourceNotFoundException() - - if job_execution is None or \ - (execution_number is not None and job_execution.execution_number != execution_number): - raise ResourceNotFoundException() - - return job_execution - - def cancel_job_execution(self, job_id, thing_name, force, expected_version, status_details): - job_execution = self.job_executions[(job_id, thing_name)] - - if job_execution is None: - raise ResourceNotFoundException() - - job_execution.force_canceled = force if force is not None else job_execution.force_canceled - # TODO: implement expected_version and status_details (at most 10 can be specified) - - if job_execution.status == 'IN_PROGRESS' and force: - job_execution.status = 'CANCELED' - self.job_executions[(job_id, thing_name)] = job_execution - elif job_execution.status != 'IN_PROGRESS': - job_execution.status = 'CANCELED' - self.job_executions[(job_id, thing_name)] = job_execution - else: - raise InvalidStateTransitionException() - - def delete_job_execution(self, job_id, thing_name, execution_number, force): - job_execution = self.job_executions[(job_id, thing_name)] - - if job_execution.execution_number != execution_number: - raise ResourceNotFoundException() - - if job_execution.status == 'IN_PROGRESS' and force: - del self.job_executions[(job_id, thing_name)] - elif job_execution.status != 'IN_PROGRESS': - del self.job_executions[(job_id, thing_name)] - else: - raise InvalidStateTransitionException() - - def list_job_executions_for_job(self, job_id, status, max_results, next_token): - job_executions = [self.job_executions[je].to_dict() for je in self.job_executions if je[0] == job_id] - - if status is not None: - job_executions = list(filter(lambda elem: - status in elem["status"] and - elem["status"] == status, job_executions)) - - token = next_token - if token is None: - job_executions = job_executions[0:max_results] - next_token = str(max_results) if len(job_executions) > max_results else None - else: - token = int(token) - job_executions = job_executions[token:token + max_results] - next_token = str(token + max_results) if len(job_executions) > token + max_results else None - - return job_executions, next_token - - def list_job_executions_for_thing(self, thing_name, status, max_results, next_token): - job_executions = [self.job_executions[je].to_dict() for je in self.job_executions if je[1] == thing_name] - - if status is not None: - job_executions = list(filter(lambda elem: - status in elem["status"] and - elem["status"] == status, job_executions)) - - token = next_token - if token is None: - job_executions = job_executions[0:max_results] - next_token = str(max_results) if len(job_executions) > max_results else None - else: - token = int(token) - job_executions = job_executions[token:token + max_results] - next_token = str(token + max_results) if len(job_executions) > token + max_results else None - - return job_executions, next_token - - -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 ( + CertificateStateException, + DeleteConflictException, + ResourceNotFoundException, + InvalidRequestException, + InvalidStateTransitionException, + 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, ca_certificate_pem=None): + 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 + self.ca_certificate_pem = ca_certificate_pem + if ca_certificate_pem: + m.update(str(uuid.uuid4()).encode('utf-8')) + self.ca_certificate_id = m.hexdigest() + + def to_dict(self): + return { + 'certificateArn': self.arn, + 'certificateId': self.certificate_id, + 'caCertificateId': self.ca_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.force = False + 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 = 'QUEUED' # IN_PROGRESS | CANCELED | COMPLETED + self.comment = None + self.reason_code = 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, + 'forceCanceled': self.force, + 'reasonCode': self.reason_code, + 'createdAt': self.created_at, + 'lastUpdatedAt': self.last_updated_at, + 'completedAt': self.completed_at, + '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_get_dict(self): + obj = { + 'jobId': self.job_id, + 'status': self.status, + 'forceCanceled': self.force_canceled, + 'statusDetails': {'detailsMap': self.status_details_map}, + 'thingArn': 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 + + def to_dict(self): + obj = { + 'jobId': self.job_id, + 'thingArn': self.thing_arn, + 'jobExecutionSummary': { + 'status': self.status, + 'queuedAt': self.queued_at, + 'startedAt': self.started_at, + 'lastUpdatedAt': self.last_updated_at, + 'executionNumber': self.execution_number, + } + } + + 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): + cert = self.describe_certificate(certificate_id) + if cert.status == 'ACTIVE': + raise CertificateStateException( + 'Certificate must be deactivated (not ACTIVE) before deletion.', certificate_id) + + certs = [k[0] for k, v in self.principal_things.items() + if self._get_principal(k[0]).certificate_id == certificate_id] + if len(certs) > 0: + raise DeleteConflictException( + 'Things must be detached before deletion (arn: %s)' % certs[0] + ) + + certs = [k[0] for k, v in self.principal_policies.items() + if self._get_principal(k[0]).certificate_id == certificate_id] + if len(certs) > 0: + raise DeleteConflictException( + 'Certificate policies must be detached before deletion (arn: %s)' % certs[0] + ) + + 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 register_certificate(self, certificate_pem, ca_certificate_pem, set_as_active, status): + certificate = FakeCertificate(certificate_pem, 'ACTIVE' if set_as_active else status, + self.region_name, ca_certificate_pem) + self.certificates[certificate.certificate_id] = certificate + return certificate + + 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): + + policies = [k[1] for k, v in self.principal_policies.items() if k[1] == policy_name] + if len(policies) > 0: + raise DeleteConflictException( + 'The policy cannot be deleted as the policy is attached to one or more principals (name=%s)' + % 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].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): + jobs = [_ for _ in self.jobs.values() if _.job_id == job_id] + if len(jobs) == 0: + raise ResourceNotFoundException() + return jobs[0] + + def delete_job(self, job_id, force): + job = self.jobs[job_id] + + if job.status == 'IN_PROGRESS' and force: + del self.jobs[job_id] + elif job.status != 'IN_PROGRESS': + del self.jobs[job_id] + else: + raise InvalidStateTransitionException() + + def cancel_job(self, job_id, reason_code, comment, force): + job = self.jobs[job_id] + + job.reason_code = reason_code if reason_code is not None else job.reason_code + job.comment = comment if comment is not None else job.comment + job.force = force if force is not None and force != job.force else job.force + job.status = 'CANCELED' + + if job.status == 'IN_PROGRESS' and force: + self.jobs[job_id] = job + elif job.status != 'IN_PROGRESS': + self.jobs[job_id] = job + else: + raise InvalidStateTransitionException() + + return job + + def get_job_document(self, job_id): + return self.jobs[job_id] + + def list_jobs(self, status, target_selection, max_results, token, thing_group_name, thing_group_id): + # TODO: implement filters + all_jobs = [_.to_dict() for _ in self.jobs.values()] + filtered_jobs = all_jobs + + if token is None: + jobs = filtered_jobs[0:max_results] + next_token = str(max_results) if len(filtered_jobs) > max_results else None + else: + token = int(token) + jobs = filtered_jobs[token:token + max_results] + next_token = str(token + max_results) if len(filtered_jobs) > token + max_results else None + + return jobs, next_token + + def describe_job_execution(self, job_id, thing_name, execution_number): + try: + job_execution = self.job_executions[(job_id, thing_name)] + except KeyError: + raise ResourceNotFoundException() + + if job_execution is None or \ + (execution_number is not None and job_execution.execution_number != execution_number): + raise ResourceNotFoundException() + + return job_execution + + def cancel_job_execution(self, job_id, thing_name, force, expected_version, status_details): + job_execution = self.job_executions[(job_id, thing_name)] + + if job_execution is None: + raise ResourceNotFoundException() + + job_execution.force_canceled = force if force is not None else job_execution.force_canceled + # TODO: implement expected_version and status_details (at most 10 can be specified) + + if job_execution.status == 'IN_PROGRESS' and force: + job_execution.status = 'CANCELED' + self.job_executions[(job_id, thing_name)] = job_execution + elif job_execution.status != 'IN_PROGRESS': + job_execution.status = 'CANCELED' + self.job_executions[(job_id, thing_name)] = job_execution + else: + raise InvalidStateTransitionException() + + def delete_job_execution(self, job_id, thing_name, execution_number, force): + job_execution = self.job_executions[(job_id, thing_name)] + + if job_execution.execution_number != execution_number: + raise ResourceNotFoundException() + + if job_execution.status == 'IN_PROGRESS' and force: + del self.job_executions[(job_id, thing_name)] + elif job_execution.status != 'IN_PROGRESS': + del self.job_executions[(job_id, thing_name)] + else: + raise InvalidStateTransitionException() + + def list_job_executions_for_job(self, job_id, status, max_results, next_token): + job_executions = [self.job_executions[je].to_dict() for je in self.job_executions if je[0] == job_id] + + if status is not None: + job_executions = list(filter(lambda elem: + status in elem["status"] and + elem["status"] == status, job_executions)) + + token = next_token + if token is None: + job_executions = job_executions[0:max_results] + next_token = str(max_results) if len(job_executions) > max_results else None + else: + token = int(token) + job_executions = job_executions[token:token + max_results] + next_token = str(token + max_results) if len(job_executions) > token + max_results else None + + return job_executions, next_token + + def list_job_executions_for_thing(self, thing_name, status, max_results, next_token): + job_executions = [self.job_executions[je].to_dict() for je in self.job_executions if je[1] == thing_name] + + if status is not None: + job_executions = list(filter(lambda elem: + status in elem["status"] and + elem["status"] == status, job_executions)) + + token = next_token + if token is None: + job_executions = job_executions[0:max_results] + next_token = str(max_results) if len(job_executions) > max_results else None + else: + token = int(token) + job_executions = job_executions[token:token + max_results] + next_token = str(token + max_results) if len(job_executions) > token + max_results else None + + 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 68a206047..8954c7003 100644 --- a/moto/iot/responses.py +++ b/moto/iot/responses.py @@ -1,613 +1,613 @@ -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, - forceCanceled=job.force, - reasonCode=job.reason_code, - 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 delete_job(self): - job_id = self._get_param("jobId") - force = self._get_bool_param("force") - - self.iot_backend.delete_job(job_id=job_id, - force=force) - - return json.dumps(dict()) - - def cancel_job(self): - job_id = self._get_param("jobId") - reason_code = self._get_param("reasonCode") - comment = self._get_param("comment") - force = self._get_bool_param("force") - - job = self.iot_backend.cancel_job(job_id=job_id, - reason_code=reason_code, - comment=comment, - force=force) - - return json.dumps(job.to_dict()) - - 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_jobs(self): - status = self._get_param("status"), - target_selection = self._get_param("targetSelection"), - max_results = self._get_int_param("maxResults", 50) # not the default, but makes testing easier - previous_next_token = self._get_param("nextToken") - thing_group_name = self._get_param("thingGroupName"), - thing_group_id = self._get_param("thingGroupId") - jobs, next_token = self.iot_backend.list_jobs(status=status, - target_selection=target_selection, - max_results=max_results, - token=previous_next_token, - thing_group_name=thing_group_name, - thing_group_id=thing_group_id) - - return json.dumps(dict(jobs=jobs, nextToken=next_token)) - - def describe_job_execution(self): - job_id = self._get_param("jobId") - thing_name = self._get_param("thingName") - execution_number = self._get_int_param("executionNumber") - job_execution = self.iot_backend.describe_job_execution(job_id=job_id, - thing_name=thing_name, - execution_number=execution_number) - - return json.dumps(dict(execution=job_execution.to_get_dict())) - - def cancel_job_execution(self): - job_id = self._get_param("jobId") - thing_name = self._get_param("thingName") - force = self._get_bool_param("force") - expected_version = self._get_int_param("expectedVersion") - status_details = self._get_param("statusDetails") - - self.iot_backend.cancel_job_execution(job_id=job_id, - thing_name=thing_name, - force=force, - expected_version=expected_version, - status_details=status_details) - - return json.dumps(dict()) - - def delete_job_execution(self): - job_id = self._get_param("jobId") - thing_name = self._get_param("thingName") - execution_number = self._get_int_param("executionNumber") - force = self._get_bool_param("force") - - self.iot_backend.delete_job_execution(job_id=job_id, - thing_name=thing_name, - execution_number=execution_number, - force=force) - - return json.dumps(dict()) - - def list_job_executions_for_job(self): - job_id = self._get_param("jobId") - status = self._get_param("status") - max_results = self._get_int_param("maxResults", 50) # not the default, but makes testing easier - next_token = self._get_param("nextToken") - job_executions, next_token = self.iot_backend.list_job_executions_for_job(job_id=job_id, - status=status, - max_results=max_results, - next_token=next_token) - - return json.dumps(dict(executionSummaries=job_executions, nextToken=next_token)) - - def list_job_executions_for_thing(self): - thing_name = self._get_param("thingName") - status = self._get_param("status") - max_results = self._get_int_param("maxResults", 50) # not the default, but makes testing easier - next_token = self._get_param("nextToken") - job_executions, next_token = self.iot_backend.list_job_executions_for_thing(thing_name=thing_name, - status=status, - max_results=max_results, - next_token=next_token) - - return json.dumps(dict(executionSummaries=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 register_certificate(self): - certificate_pem = self._get_param("certificatePem") - ca_certificate_pem = self._get_param("caCertificatePem") - set_as_active = self._get_bool_param("setAsActive") - status = self._get_param("status") - - cert = self.iot_backend.register_certificate( - certificate_pem=certificate_pem, - ca_certificate_pem=ca_certificate_pem, - set_as_active=set_as_active, - status=status - ) - return json.dumps(dict(certificateId=cert.certificate_id, certificateArn=cert.arn)) - - 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") - target = self._get_param('target') - self.iot_backend.attach_policy( - policy_name=policy_name, - target=target, - ) - 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_policy(self): - policy_name = self._get_param("policyName") - target = self._get_param('target') - self.iot_backend.detach_policy( - policy_name=policy_name, - target=target, - ) - 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, + forceCanceled=job.force, + reasonCode=job.reason_code, + 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 delete_job(self): + job_id = self._get_param("jobId") + force = self._get_bool_param("force") + + self.iot_backend.delete_job(job_id=job_id, + force=force) + + return json.dumps(dict()) + + def cancel_job(self): + job_id = self._get_param("jobId") + reason_code = self._get_param("reasonCode") + comment = self._get_param("comment") + force = self._get_bool_param("force") + + job = self.iot_backend.cancel_job(job_id=job_id, + reason_code=reason_code, + comment=comment, + force=force) + + return json.dumps(job.to_dict()) + + 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_jobs(self): + status = self._get_param("status"), + target_selection = self._get_param("targetSelection"), + max_results = self._get_int_param("maxResults", 50) # not the default, but makes testing easier + previous_next_token = self._get_param("nextToken") + thing_group_name = self._get_param("thingGroupName"), + thing_group_id = self._get_param("thingGroupId") + jobs, next_token = self.iot_backend.list_jobs(status=status, + target_selection=target_selection, + max_results=max_results, + token=previous_next_token, + thing_group_name=thing_group_name, + thing_group_id=thing_group_id) + + return json.dumps(dict(jobs=jobs, nextToken=next_token)) + + def describe_job_execution(self): + job_id = self._get_param("jobId") + thing_name = self._get_param("thingName") + execution_number = self._get_int_param("executionNumber") + job_execution = self.iot_backend.describe_job_execution(job_id=job_id, + thing_name=thing_name, + execution_number=execution_number) + + return json.dumps(dict(execution=job_execution.to_get_dict())) + + def cancel_job_execution(self): + job_id = self._get_param("jobId") + thing_name = self._get_param("thingName") + force = self._get_bool_param("force") + expected_version = self._get_int_param("expectedVersion") + status_details = self._get_param("statusDetails") + + self.iot_backend.cancel_job_execution(job_id=job_id, + thing_name=thing_name, + force=force, + expected_version=expected_version, + status_details=status_details) + + return json.dumps(dict()) + + def delete_job_execution(self): + job_id = self._get_param("jobId") + thing_name = self._get_param("thingName") + execution_number = self._get_int_param("executionNumber") + force = self._get_bool_param("force") + + self.iot_backend.delete_job_execution(job_id=job_id, + thing_name=thing_name, + execution_number=execution_number, + force=force) + + return json.dumps(dict()) + + def list_job_executions_for_job(self): + job_id = self._get_param("jobId") + status = self._get_param("status") + max_results = self._get_int_param("maxResults", 50) # not the default, but makes testing easier + next_token = self._get_param("nextToken") + job_executions, next_token = self.iot_backend.list_job_executions_for_job(job_id=job_id, + status=status, + max_results=max_results, + next_token=next_token) + + return json.dumps(dict(executionSummaries=job_executions, nextToken=next_token)) + + def list_job_executions_for_thing(self): + thing_name = self._get_param("thingName") + status = self._get_param("status") + max_results = self._get_int_param("maxResults", 50) # not the default, but makes testing easier + next_token = self._get_param("nextToken") + job_executions, next_token = self.iot_backend.list_job_executions_for_thing(thing_name=thing_name, + status=status, + max_results=max_results, + next_token=next_token) + + return json.dumps(dict(executionSummaries=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 register_certificate(self): + certificate_pem = self._get_param("certificatePem") + ca_certificate_pem = self._get_param("caCertificatePem") + set_as_active = self._get_bool_param("setAsActive") + status = self._get_param("status") + + cert = self.iot_backend.register_certificate( + certificate_pem=certificate_pem, + ca_certificate_pem=ca_certificate_pem, + set_as_active=set_as_active, + status=status + ) + return json.dumps(dict(certificateId=cert.certificate_id, certificateArn=cert.arn)) + + 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") + target = self._get_param('target') + self.iot_backend.attach_policy( + policy_name=policy_name, + target=target, + ) + 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_policy(self): + policy_name = self._get_param("policyName") + target = self._get_param('target') + self.iot_backend.detach_policy( + policy_name=policy_name, + target=target, + ) + 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 8f11912b0..4a142b292 100644 --- a/tests/test_iot/test_iot.py +++ b/tests/test_iot/test_iot.py @@ -1,1409 +1,1409 @@ -from __future__ import unicode_literals - -import json -import sure #noqa -import boto3 - -from moto import mock_iot -from botocore.exceptions import ClientError -from nose.tools import assert_raises - -@mock_iot -def test_attach_policy(): - client = boto3.client('iot', region_name='ap-northeast-1') - policy_name = 'my-policy' - doc = '{}' - - cert = client.create_keys_and_certificate(setAsActive=True) - cert_arn = cert['certificateArn'] - client.create_policy(policyName=policy_name, policyDocument=doc) - client.attach_policy(policyName=policy_name, target=cert_arn) - - res = client.list_attached_policies(target=cert_arn) - res.should.have.key('policies').which.should.have.length_of(1) - res['policies'][0]['policyName'].should.equal('my-policy') - - -@mock_iot -def test_detach_policy(): - client = boto3.client('iot', region_name='ap-northeast-1') - policy_name = 'my-policy' - doc = '{}' - - cert = client.create_keys_and_certificate(setAsActive=True) - cert_arn = cert['certificateArn'] - client.create_policy(policyName=policy_name, policyDocument=doc) - client.attach_policy(policyName=policy_name, target=cert_arn) - - res = client.list_attached_policies(target=cert_arn) - res.should.have.key('policies').which.should.have.length_of(1) - res['policies'][0]['policyName'].should.equal('my-policy') - - client.detach_policy(policyName=policy_name, target=cert_arn) - res = client.list_attached_policies(target=cert_arn) - res.should.have.key('policies').which.should.be.empty - - -@mock_iot -def test_list_attached_policies(): - client = boto3.client('iot', region_name='ap-northeast-1') - cert = client.create_keys_and_certificate(setAsActive=True) - policies = client.list_attached_policies(target=cert['certificateArn']) - policies['policies'].should.be.empty - - -@mock_iot -def test_policy_versions(): - client = boto3.client('iot', region_name='ap-northeast-1') - policy_name = 'my-policy' - doc = '{}' - - policy = client.create_policy(policyName=policy_name, policyDocument=doc) - policy.should.have.key('policyName').which.should.equal(policy_name) - policy.should.have.key('policyArn').which.should_not.be.none - policy.should.have.key('policyDocument').which.should.equal(json.dumps({})) - policy.should.have.key('policyVersionId').which.should.equal('1') - - policy = client.get_policy(policyName=policy_name) - policy.should.have.key('policyName').which.should.equal(policy_name) - policy.should.have.key('policyArn').which.should_not.be.none - policy.should.have.key('policyDocument').which.should.equal(json.dumps({})) - policy.should.have.key('defaultVersionId').which.should.equal(policy['defaultVersionId']) - - policy1 = client.create_policy_version(policyName=policy_name, policyDocument=json.dumps({'version': 'version_1'}), - setAsDefault=True) - policy1.should.have.key('policyArn').which.should_not.be.none - policy1.should.have.key('policyDocument').which.should.equal(json.dumps({'version': 'version_1'})) - policy1.should.have.key('policyVersionId').which.should.equal('2') - policy1.should.have.key('isDefaultVersion').which.should.equal(True) - - policy2 = client.create_policy_version(policyName=policy_name, policyDocument=json.dumps({'version': 'version_2'}), - setAsDefault=False) - policy2.should.have.key('policyArn').which.should_not.be.none - policy2.should.have.key('policyDocument').which.should.equal(json.dumps({'version': 'version_2'})) - policy2.should.have.key('policyVersionId').which.should.equal('3') - policy2.should.have.key('isDefaultVersion').which.should.equal(False) - - policy = client.get_policy(policyName=policy_name) - policy.should.have.key('policyName').which.should.equal(policy_name) - policy.should.have.key('policyArn').which.should_not.be.none - policy.should.have.key('policyDocument').which.should.equal(json.dumps({'version': 'version_1'})) - policy.should.have.key('defaultVersionId').which.should.equal(policy1['policyVersionId']) - - policy_versions = client.list_policy_versions(policyName=policy_name) - policy_versions.should.have.key('policyVersions').which.should.have.length_of(3) - list(map(lambda item: item['isDefaultVersion'], policy_versions['policyVersions'])).count(True).should.equal(1) - default_policy = list(filter(lambda item: item['isDefaultVersion'], policy_versions['policyVersions'])) - default_policy[0].should.have.key('versionId').should.equal(policy1['policyVersionId']) - - policy = client.get_policy(policyName=policy_name) - policy.should.have.key('policyName').which.should.equal(policy_name) - policy.should.have.key('policyArn').which.should_not.be.none - policy.should.have.key('policyDocument').which.should.equal(json.dumps({'version': 'version_1'})) - policy.should.have.key('defaultVersionId').which.should.equal(policy1['policyVersionId']) - - client.set_default_policy_version(policyName=policy_name, policyVersionId=policy2['policyVersionId']) - policy_versions = client.list_policy_versions(policyName=policy_name) - policy_versions.should.have.key('policyVersions').which.should.have.length_of(3) - list(map(lambda item: item['isDefaultVersion'], policy_versions['policyVersions'])).count(True).should.equal(1) - default_policy = list(filter(lambda item: item['isDefaultVersion'], policy_versions['policyVersions'])) - default_policy[0].should.have.key('versionId').should.equal(policy2['policyVersionId']) - - policy = client.get_policy(policyName=policy_name) - policy.should.have.key('policyName').which.should.equal(policy_name) - policy.should.have.key('policyArn').which.should_not.be.none - policy.should.have.key('policyDocument').which.should.equal(json.dumps({'version': 'version_2'})) - policy.should.have.key('defaultVersionId').which.should.equal(policy2['policyVersionId']) - - client.delete_policy_version(policyName=policy_name, policyVersionId='1') - policy_versions = client.list_policy_versions(policyName=policy_name) - policy_versions.should.have.key('policyVersions').which.should.have.length_of(2) - - client.delete_policy_version(policyName=policy_name, policyVersionId=policy1['policyVersionId']) - policy_versions = client.list_policy_versions(policyName=policy_name) - policy_versions.should.have.key('policyVersions').which.should.have.length_of(1) - - # should fail as it's the default policy. Should use delete_policy instead - try: - client.delete_policy_version(policyName=policy_name, policyVersionId=policy2['policyVersionId']) - assert False, 'Should have failed in previous call' - except Exception as exception: - exception.response['Error']['Message'].should.equal('Cannot delete the default version of a policy') - - -@mock_iot -def test_things(): - client = boto3.client('iot', region_name='ap-northeast-1') - name = 'my-thing' - type_name = 'my-type-name' - - # thing type - thing_type = client.create_thing_type(thingTypeName=type_name) - thing_type.should.have.key('thingTypeName').which.should.equal(type_name) - thing_type.should.have.key('thingTypeArn') - - res = client.list_thing_types() - res.should.have.key('thingTypes').which.should.have.length_of(1) - for thing_type in res['thingTypes']: - thing_type.should.have.key('thingTypeName').which.should_not.be.none - - thing_type = client.describe_thing_type(thingTypeName=type_name) - thing_type.should.have.key('thingTypeName').which.should.equal(type_name) - thing_type.should.have.key('thingTypeProperties') - thing_type.should.have.key('thingTypeMetadata') - - # thing - thing = client.create_thing(thingName=name, thingTypeName=type_name) - thing.should.have.key('thingName').which.should.equal(name) - thing.should.have.key('thingArn') - res = client.list_things() - res.should.have.key('things').which.should.have.length_of(1) - for thing in res['things']: - thing.should.have.key('thingName').which.should_not.be.none - thing.should.have.key('thingArn').which.should_not.be.none - - thing = client.update_thing(thingName=name, attributePayload={'attributes': {'k1': 'v1'}}) - res = client.list_things() - res.should.have.key('things').which.should.have.length_of(1) - for thing in res['things']: - thing.should.have.key('thingName').which.should_not.be.none - thing.should.have.key('thingArn').which.should_not.be.none - res['things'][0]['attributes'].should.have.key('k1').which.should.equal('v1') - - thing = client.describe_thing(thingName=name) - thing.should.have.key('thingName').which.should.equal(name) - thing.should.have.key('defaultClientId') - thing.should.have.key('thingTypeName') - thing.should.have.key('attributes') - thing.should.have.key('version') - - # delete thing - client.delete_thing(thingName=name) - res = client.list_things() - res.should.have.key('things').which.should.have.length_of(0) - - # delete thing type - client.delete_thing_type(thingTypeName=type_name) - res = client.list_thing_types() - res.should.have.key('thingTypes').which.should.have.length_of(0) - - -@mock_iot -def test_list_thing_types(): - client = boto3.client('iot', region_name='ap-northeast-1') - - for i in range(0, 100): - client.create_thing_type(thingTypeName=str(i + 1)) - - thing_types = client.list_thing_types() - thing_types.should.have.key('nextToken') - thing_types.should.have.key('thingTypes').which.should.have.length_of(50) - thing_types['thingTypes'][0]['thingTypeName'].should.equal('1') - thing_types['thingTypes'][-1]['thingTypeName'].should.equal('50') - - thing_types = client.list_thing_types(nextToken=thing_types['nextToken']) - thing_types.should.have.key('thingTypes').which.should.have.length_of(50) - thing_types.should_not.have.key('nextToken') - thing_types['thingTypes'][0]['thingTypeName'].should.equal('51') - thing_types['thingTypes'][-1]['thingTypeName'].should.equal('100') - - -@mock_iot -def test_list_thing_types_with_typename_filter(): - client = boto3.client('iot', region_name='ap-northeast-1') - - client.create_thing_type(thingTypeName='thing') - client.create_thing_type(thingTypeName='thingType') - client.create_thing_type(thingTypeName='thingTypeName') - client.create_thing_type(thingTypeName='thingTypeNameGroup') - client.create_thing_type(thingTypeName='shouldNotFind') - client.create_thing_type(thingTypeName='find me it shall not') - - thing_types = client.list_thing_types(thingTypeName='thing') - thing_types.should_not.have.key('nextToken') - thing_types.should.have.key('thingTypes').which.should.have.length_of(4) - thing_types['thingTypes'][0]['thingTypeName'].should.equal('thing') - thing_types['thingTypes'][-1]['thingTypeName'].should.equal('thingTypeNameGroup') - - thing_types = client.list_thing_types(thingTypeName='thingTypeName') - thing_types.should_not.have.key('nextToken') - thing_types.should.have.key('thingTypes').which.should.have.length_of(2) - thing_types['thingTypes'][0]['thingTypeName'].should.equal('thingTypeName') - thing_types['thingTypes'][-1]['thingTypeName'].should.equal('thingTypeNameGroup') - - -@mock_iot -def test_list_things_with_next_token(): - client = boto3.client('iot', region_name='ap-northeast-1') - - for i in range(0, 200): - client.create_thing(thingName=str(i + 1)) - - things = client.list_things() - things.should.have.key('nextToken') - things.should.have.key('things').which.should.have.length_of(50) - things['things'][0]['thingName'].should.equal('1') - things['things'][0]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/1') - things['things'][-1]['thingName'].should.equal('50') - things['things'][-1]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/50') - - things = client.list_things(nextToken=things['nextToken']) - things.should.have.key('nextToken') - things.should.have.key('things').which.should.have.length_of(50) - things['things'][0]['thingName'].should.equal('51') - things['things'][0]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/51') - things['things'][-1]['thingName'].should.equal('100') - things['things'][-1]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/100') - - things = client.list_things(nextToken=things['nextToken']) - things.should.have.key('nextToken') - things.should.have.key('things').which.should.have.length_of(50) - things['things'][0]['thingName'].should.equal('101') - things['things'][0]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/101') - things['things'][-1]['thingName'].should.equal('150') - things['things'][-1]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/150') - - things = client.list_things(nextToken=things['nextToken']) - things.should_not.have.key('nextToken') - things.should.have.key('things').which.should.have.length_of(50) - things['things'][0]['thingName'].should.equal('151') - things['things'][0]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/151') - things['things'][-1]['thingName'].should.equal('200') - things['things'][-1]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/200') - - -@mock_iot -def test_list_things_with_attribute_and_thing_type_filter_and_next_token(): - client = boto3.client('iot', region_name='ap-northeast-1') - client.create_thing_type(thingTypeName='my-thing-type') - - for i in range(0, 200): - if not (i + 1) % 3: - attribute_payload = { - 'attributes': { - 'foo': 'bar' - } - } - elif not (i + 1) % 5: - attribute_payload = { - 'attributes': { - 'bar': 'foo' - } - } - else: - attribute_payload = {} - - if not (i + 1) % 2: - thing_type_name = 'my-thing-type' - client.create_thing(thingName=str(i + 1), thingTypeName=thing_type_name, attributePayload=attribute_payload) - else: - client.create_thing(thingName=str(i + 1), attributePayload=attribute_payload) - - # Test filter for thingTypeName - things = client.list_things(thingTypeName=thing_type_name) - things.should.have.key('nextToken') - things.should.have.key('things').which.should.have.length_of(50) - things['things'][0]['thingName'].should.equal('2') - things['things'][0]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/2') - things['things'][-1]['thingName'].should.equal('100') - things['things'][-1]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/100') - all(item['thingTypeName'] == thing_type_name for item in things['things']) - - things = client.list_things(nextToken=things['nextToken'], thingTypeName=thing_type_name) - things.should_not.have.key('nextToken') - things.should.have.key('things').which.should.have.length_of(50) - things['things'][0]['thingName'].should.equal('102') - things['things'][0]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/102') - things['things'][-1]['thingName'].should.equal('200') - things['things'][-1]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/200') - all(item['thingTypeName'] == thing_type_name for item in things['things']) - - # Test filter for attributes - things = client.list_things(attributeName='foo', attributeValue='bar') - things.should.have.key('nextToken') - things.should.have.key('things').which.should.have.length_of(50) - things['things'][0]['thingName'].should.equal('3') - things['things'][0]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/3') - things['things'][-1]['thingName'].should.equal('150') - things['things'][-1]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/150') - all(item['attributes'] == {'foo': 'bar'} for item in things['things']) - - things = client.list_things(nextToken=things['nextToken'], attributeName='foo', attributeValue='bar') - things.should_not.have.key('nextToken') - things.should.have.key('things').which.should.have.length_of(16) - things['things'][0]['thingName'].should.equal('153') - things['things'][0]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/153') - things['things'][-1]['thingName'].should.equal('198') - things['things'][-1]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/198') - all(item['attributes'] == {'foo': 'bar'} for item in things['things']) - - # Test filter for attributes and thingTypeName - things = client.list_things(thingTypeName=thing_type_name, attributeName='foo', attributeValue='bar') - things.should_not.have.key('nextToken') - things.should.have.key('things').which.should.have.length_of(33) - things['things'][0]['thingName'].should.equal('6') - things['things'][0]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/6') - things['things'][-1]['thingName'].should.equal('198') - things['things'][-1]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/198') - all(item['attributes'] == {'foo': 'bar'} and item['thingTypeName'] == thing_type_name for item in things['things']) - - -@mock_iot -def test_certs(): - client = boto3.client('iot', region_name='us-east-1') - cert = client.create_keys_and_certificate(setAsActive=True) - cert.should.have.key('certificateArn').which.should_not.be.none - cert.should.have.key('certificateId').which.should_not.be.none - cert.should.have.key('certificatePem').which.should_not.be.none - cert.should.have.key('keyPair') - cert['keyPair'].should.have.key('PublicKey').which.should_not.be.none - cert['keyPair'].should.have.key('PrivateKey').which.should_not.be.none - cert_id = cert['certificateId'] - - cert = client.describe_certificate(certificateId=cert_id) - cert.should.have.key('certificateDescription') - cert_desc = cert['certificateDescription'] - cert_desc.should.have.key('certificateArn').which.should_not.be.none - cert_desc.should.have.key('certificateId').which.should_not.be.none - cert_desc.should.have.key('certificatePem').which.should_not.be.none - cert_desc.should.have.key('status').which.should.equal('ACTIVE') - cert_pem = cert_desc['certificatePem'] - - res = client.list_certificates() - for cert in res['certificates']: - cert.should.have.key('certificateArn').which.should_not.be.none - cert.should.have.key('certificateId').which.should_not.be.none - cert.should.have.key('status').which.should_not.be.none - cert.should.have.key('creationDate').which.should_not.be.none - - client.update_certificate(certificateId=cert_id, newStatus='REVOKED') - cert = client.describe_certificate(certificateId=cert_id) - cert_desc = cert['certificateDescription'] - cert_desc.should.have.key('status').which.should.equal('REVOKED') - - client.delete_certificate(certificateId=cert_id) - res = client.list_certificates() - res.should.have.key('certificates') - - # Test register_certificate flow - cert = client.register_certificate(certificatePem=cert_pem, setAsActive=True) - cert.should.have.key('certificateId').which.should_not.be.none - cert.should.have.key('certificateArn').which.should_not.be.none - cert_id = cert['certificateId'] - - res = client.list_certificates() - res.should.have.key('certificates').which.should.have.length_of(1) - for cert in res['certificates']: - cert.should.have.key('certificateArn').which.should_not.be.none - cert.should.have.key('certificateId').which.should_not.be.none - cert.should.have.key('status').which.should_not.be.none - cert.should.have.key('creationDate').which.should_not.be.none - - client.update_certificate(certificateId=cert_id, newStatus='REVOKED') - cert = client.describe_certificate(certificateId=cert_id) - cert_desc = cert['certificateDescription'] - cert_desc.should.have.key('status').which.should.equal('REVOKED') - - client.delete_certificate(certificateId=cert_id) - res = client.list_certificates() - res.should.have.key('certificates') - - -@mock_iot -def test_delete_policy_validation(): - doc = """{ - "Version": "2012-10-17", - "Statement":[ - { - "Effect":"Allow", - "Action":[ - "iot: *" - ], - "Resource":"*" - } - ] - } - """ - client = boto3.client('iot', region_name='ap-northeast-1') - cert = client.create_keys_and_certificate(setAsActive=True) - cert_arn = cert['certificateArn'] - policy_name = 'my-policy' - client.create_policy(policyName=policy_name, policyDocument=doc) - client.attach_principal_policy(policyName=policy_name, principal=cert_arn) - - with assert_raises(ClientError) as e: - client.delete_policy(policyName=policy_name) - e.exception.response['Error']['Message'].should.contain( - 'The policy cannot be deleted as the policy is attached to one or more principals (name=%s)' % policy_name) - res = client.list_policies() - res.should.have.key('policies').which.should.have.length_of(1) - - client.detach_principal_policy(policyName=policy_name, principal=cert_arn) - client.delete_policy(policyName=policy_name) - res = client.list_policies() - res.should.have.key('policies').which.should.have.length_of(0) - - -@mock_iot -def test_delete_certificate_validation(): - doc = """{ - "Version": "2012-10-17", - "Statement":[ - { - "Effect":"Allow", - "Action":[ - "iot: *" - ], - "Resource":"*" - } - ] - } - """ - client = boto3.client('iot', region_name='ap-northeast-1') - cert = client.create_keys_and_certificate(setAsActive=True) - cert_id = cert['certificateId'] - cert_arn = cert['certificateArn'] - policy_name = 'my-policy' - thing_name = 'thing-1' - client.create_policy(policyName=policy_name, policyDocument=doc) - client.attach_principal_policy(policyName=policy_name, principal=cert_arn) - client.create_thing(thingName=thing_name) - client.attach_thing_principal(thingName=thing_name, principal=cert_arn) - - with assert_raises(ClientError) as e: - client.delete_certificate(certificateId=cert_id) - e.exception.response['Error']['Message'].should.contain( - 'Certificate must be deactivated (not ACTIVE) before deletion.') - res = client.list_certificates() - res.should.have.key('certificates').which.should.have.length_of(1) - - client.update_certificate(certificateId=cert_id, newStatus='REVOKED') - with assert_raises(ClientError) as e: - client.delete_certificate(certificateId=cert_id) - e.exception.response['Error']['Message'].should.contain( - 'Things must be detached before deletion (arn: %s)' % cert_arn) - res = client.list_certificates() - res.should.have.key('certificates').which.should.have.length_of(1) - - client.detach_thing_principal(thingName=thing_name, principal=cert_arn) - with assert_raises(ClientError) as e: - client.delete_certificate(certificateId=cert_id) - e.exception.response['Error']['Message'].should.contain( - 'Certificate policies must be detached before deletion (arn: %s)' % cert_arn) - res = client.list_certificates() - res.should.have.key('certificates').which.should.have.length_of(1) - - client.detach_principal_policy(policyName=policy_name, principal=cert_arn) - client.delete_certificate(certificateId=cert_id) - res = client.list_certificates() - res.should.have.key('certificates').which.should.have.length_of(0) - - -@mock_iot -def test_certs_create_inactive(): - client = boto3.client('iot', region_name='ap-northeast-1') - cert = client.create_keys_and_certificate(setAsActive=False) - cert_id = cert['certificateId'] - - cert = client.describe_certificate(certificateId=cert_id) - cert.should.have.key('certificateDescription') - cert_desc = cert['certificateDescription'] - cert_desc.should.have.key('status').which.should.equal('INACTIVE') - - client.update_certificate(certificateId=cert_id, newStatus='ACTIVE') - cert = client.describe_certificate(certificateId=cert_id) - cert.should.have.key('certificateDescription') - cert_desc = cert['certificateDescription'] - cert_desc.should.have.key('status').which.should.equal('ACTIVE') - - -@mock_iot -def test_policy(): - client = boto3.client('iot', region_name='ap-northeast-1') - name = 'my-policy' - doc = '{}' - policy = client.create_policy(policyName=name, policyDocument=doc) - policy.should.have.key('policyName').which.should.equal(name) - policy.should.have.key('policyArn').which.should_not.be.none - policy.should.have.key('policyDocument').which.should.equal(doc) - policy.should.have.key('policyVersionId').which.should.equal('1') - - policy = client.get_policy(policyName=name) - policy.should.have.key('policyName').which.should.equal(name) - policy.should.have.key('policyArn').which.should_not.be.none - policy.should.have.key('policyDocument').which.should.equal(doc) - policy.should.have.key('defaultVersionId').which.should.equal('1') - - res = client.list_policies() - res.should.have.key('policies').which.should.have.length_of(1) - for policy in res['policies']: - policy.should.have.key('policyName').which.should_not.be.none - policy.should.have.key('policyArn').which.should_not.be.none - - client.delete_policy(policyName=name) - res = client.list_policies() - res.should.have.key('policies').which.should.have.length_of(0) - - -@mock_iot -def test_principal_policy(): - client = boto3.client('iot', region_name='ap-northeast-1') - policy_name = 'my-policy' - doc = '{}' - client.create_policy(policyName=policy_name, policyDocument=doc) - cert = client.create_keys_and_certificate(setAsActive=True) - cert_arn = cert['certificateArn'] - - client.attach_policy(policyName=policy_name, target=cert_arn) - - res = client.list_principal_policies(principal=cert_arn) - res.should.have.key('policies').which.should.have.length_of(1) - for policy in res['policies']: - policy.should.have.key('policyName').which.should_not.be.none - policy.should.have.key('policyArn').which.should_not.be.none - - # do nothing if policy have already attached to certificate - client.attach_policy(policyName=policy_name, target=cert_arn) - - res = client.list_principal_policies(principal=cert_arn) - res.should.have.key('policies').which.should.have.length_of(1) - for policy in res['policies']: - policy.should.have.key('policyName').which.should_not.be.none - policy.should.have.key('policyArn').which.should_not.be.none - - res = client.list_policy_principals(policyName=policy_name) - res.should.have.key('principals').which.should.have.length_of(1) - for principal in res['principals']: - principal.should_not.be.none - - client.detach_policy(policyName=policy_name, target=cert_arn) - res = client.list_principal_policies(principal=cert_arn) - res.should.have.key('policies').which.should.have.length_of(0) - res = client.list_policy_principals(policyName=policy_name) - res.should.have.key('principals').which.should.have.length_of(0) - with assert_raises(ClientError) as e: - client.detach_policy(policyName=policy_name, target=cert_arn) - e.exception.response['Error']['Code'].should.equal('ResourceNotFoundException') - - -@mock_iot -def test_principal_policy_deprecated(): - client = boto3.client('iot', region_name='ap-northeast-1') - policy_name = 'my-policy' - doc = '{}' - policy = client.create_policy(policyName=policy_name, policyDocument=doc) - cert = client.create_keys_and_certificate(setAsActive=True) - cert_arn = cert['certificateArn'] - - client.attach_principal_policy(policyName=policy_name, principal=cert_arn) - - res = client.list_principal_policies(principal=cert_arn) - res.should.have.key('policies').which.should.have.length_of(1) - for policy in res['policies']: - policy.should.have.key('policyName').which.should_not.be.none - policy.should.have.key('policyArn').which.should_not.be.none - - res = client.list_policy_principals(policyName=policy_name) - res.should.have.key('principals').which.should.have.length_of(1) - for principal in res['principals']: - principal.should_not.be.none - - client.detach_principal_policy(policyName=policy_name, principal=cert_arn) - res = client.list_principal_policies(principal=cert_arn) - res.should.have.key('policies').which.should.have.length_of(0) - res = client.list_policy_principals(policyName=policy_name) - res.should.have.key('principals').which.should.have.length_of(0) - - -@mock_iot -def test_principal_thing(): - client = boto3.client('iot', region_name='ap-northeast-1') - thing_name = 'my-thing' - thing = client.create_thing(thingName=thing_name) - cert = client.create_keys_and_certificate(setAsActive=True) - cert_arn = cert['certificateArn'] - - client.attach_thing_principal(thingName=thing_name, principal=cert_arn) - - res = client.list_principal_things(principal=cert_arn) - res.should.have.key('things').which.should.have.length_of(1) - for thing in res['things']: - thing.should_not.be.none - res = client.list_thing_principals(thingName=thing_name) - res.should.have.key('principals').which.should.have.length_of(1) - for principal in res['principals']: - principal.should_not.be.none - - client.detach_thing_principal(thingName=thing_name, principal=cert_arn) - res = client.list_principal_things(principal=cert_arn) - res.should.have.key('things').which.should.have.length_of(0) - res = client.list_thing_principals(thingName=thing_name) - res.should.have.key('principals').which.should.have.length_of(0) - - -@mock_iot -def test_thing_groups(): - client = boto3.client('iot', region_name='ap-northeast-1') - group_name = 'my-group-name' - - # thing group - thing_group = client.create_thing_group(thingGroupName=group_name) - thing_group.should.have.key('thingGroupName').which.should.equal(group_name) - thing_group.should.have.key('thingGroupArn') - - res = client.list_thing_groups() - res.should.have.key('thingGroups').which.should.have.length_of(1) - for thing_group in res['thingGroups']: - thing_group.should.have.key('groupName').which.should_not.be.none - thing_group.should.have.key('groupArn').which.should_not.be.none - - thing_group = client.describe_thing_group(thingGroupName=group_name) - thing_group.should.have.key('thingGroupName').which.should.equal(group_name) - thing_group.should.have.key('thingGroupProperties') - thing_group.should.have.key('thingGroupMetadata') - thing_group.should.have.key('version') - - # delete thing group - client.delete_thing_group(thingGroupName=group_name) - res = client.list_thing_groups() - res.should.have.key('thingGroups').which.should.have.length_of(0) - - # props create test - props = { - 'thingGroupDescription': 'my first thing group', - 'attributePayload': { - 'attributes': { - 'key1': 'val01', - 'Key02': 'VAL2' - } - } - } - thing_group = client.create_thing_group(thingGroupName=group_name, thingGroupProperties=props) - thing_group.should.have.key('thingGroupName').which.should.equal(group_name) - thing_group.should.have.key('thingGroupArn') - - thing_group = client.describe_thing_group(thingGroupName=group_name) - thing_group.should.have.key('thingGroupProperties') \ - .which.should.have.key('attributePayload') \ - .which.should.have.key('attributes') - res_props = thing_group['thingGroupProperties']['attributePayload']['attributes'] - res_props.should.have.key('key1').which.should.equal('val01') - res_props.should.have.key('Key02').which.should.equal('VAL2') - - # props update test with merge - new_props = { - 'attributePayload': { - 'attributes': { - 'k3': 'v3' - }, - 'merge': True - } - } - client.update_thing_group( - thingGroupName=group_name, - thingGroupProperties=new_props - ) - thing_group = client.describe_thing_group(thingGroupName=group_name) - thing_group.should.have.key('thingGroupProperties') \ - .which.should.have.key('attributePayload') \ - .which.should.have.key('attributes') - res_props = thing_group['thingGroupProperties']['attributePayload']['attributes'] - res_props.should.have.key('key1').which.should.equal('val01') - res_props.should.have.key('Key02').which.should.equal('VAL2') - - res_props.should.have.key('k3').which.should.equal('v3') - - # props update test - new_props = { - 'attributePayload': { - 'attributes': { - 'k4': 'v4' - } - } - } - client.update_thing_group( - thingGroupName=group_name, - thingGroupProperties=new_props - ) - thing_group = client.describe_thing_group(thingGroupName=group_name) - thing_group.should.have.key('thingGroupProperties') \ - .which.should.have.key('attributePayload') \ - .which.should.have.key('attributes') - res_props = thing_group['thingGroupProperties']['attributePayload']['attributes'] - res_props.should.have.key('k4').which.should.equal('v4') - res_props.should_not.have.key('key1') - - -@mock_iot -def test_thing_group_relations(): - client = boto3.client('iot', region_name='ap-northeast-1') - name = 'my-thing' - group_name = 'my-group-name' - - # thing group - thing_group = client.create_thing_group(thingGroupName=group_name) - thing_group.should.have.key('thingGroupName').which.should.equal(group_name) - thing_group.should.have.key('thingGroupArn') - - # thing - thing = client.create_thing(thingName=name) - thing.should.have.key('thingName').which.should.equal(name) - thing.should.have.key('thingArn') - - # add in 4 way - client.add_thing_to_thing_group( - thingGroupName=group_name, - thingName=name - ) - client.add_thing_to_thing_group( - thingGroupArn=thing_group['thingGroupArn'], - thingArn=thing['thingArn'] - ) - client.add_thing_to_thing_group( - thingGroupName=group_name, - thingArn=thing['thingArn'] - ) - client.add_thing_to_thing_group( - thingGroupArn=thing_group['thingGroupArn'], - thingName=name - ) - - things = client.list_things_in_thing_group( - thingGroupName=group_name - ) - things.should.have.key('things') - things['things'].should.have.length_of(1) - - thing_groups = client.list_thing_groups_for_thing( - thingName=name - ) - thing_groups.should.have.key('thingGroups') - thing_groups['thingGroups'].should.have.length_of(1) - - # remove in 4 way - client.remove_thing_from_thing_group( - thingGroupName=group_name, - thingName=name - ) - client.remove_thing_from_thing_group( - thingGroupArn=thing_group['thingGroupArn'], - thingArn=thing['thingArn'] - ) - client.remove_thing_from_thing_group( - thingGroupName=group_name, - thingArn=thing['thingArn'] - ) - client.remove_thing_from_thing_group( - thingGroupArn=thing_group['thingGroupArn'], - thingName=name - ) - things = client.list_things_in_thing_group( - thingGroupName=group_name - ) - things.should.have.key('things') - things['things'].should.have.length_of(0) - - # update thing group for thing - client.update_thing_groups_for_thing( - thingName=name, - thingGroupsToAdd=[ - group_name - ] - ) - things = client.list_things_in_thing_group( - thingGroupName=group_name - ) - things.should.have.key('things') - things['things'].should.have.length_of(1) - - client.update_thing_groups_for_thing( - thingName=name, - thingGroupsToRemove=[ - group_name - ] - ) - things = client.list_things_in_thing_group( - thingGroupName=group_name - ) - things.should.have.key('things') - things['things'].should.have.length_of(0) - - -@mock_iot -def test_create_job(): - client = boto3.client('iot', region_name='eu-west-1') - name = "my-thing" - job_id = "TestJob" - # thing# job document - # job_document = { - # "field": "value" - # } - 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') - - -@mock_iot -def test_list_jobs(): - client = boto3.client('iot', region_name='eu-west-1') - name = "my-thing" - job_id = "TestJob" - # thing# job document - # job_document = { - # "field": "value" - # } - 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" - } - - job1 = 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 - } - ) - - job1.should.have.key('jobId').which.should.equal(job_id) - job1.should.have.key('jobArn') - job1.should.have.key('description') - - job2 = client.create_job( - jobId=job_id+"1", - 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 - } - ) - - job2.should.have.key('jobId').which.should.equal(job_id+"1") - job2.should.have.key('jobArn') - job2.should.have.key('description') - - jobs = client.list_jobs() - jobs.should.have.key('jobs') - jobs.should_not.have.key('nextToken') - jobs['jobs'][0].should.have.key('jobId').which.should.equal(job_id) - jobs['jobs'][1].should.have.key('jobId').which.should.equal(job_id+"1") - - -@mock_iot -def test_describe_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 = client.create_job( - jobId=job_id, - targets=[thing["thingArn"]], - documentSource="https://s3-eu-west-1.amazonaws.com/bucket-name/job_document.json", - 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 = client.describe_job(jobId=job_id) - job.should.have.key('documentSource') - job.should.have.key('job') - job.should.have.key('job').which.should.have.key("jobArn") - job.should.have.key('job').which.should.have.key("jobId").which.should.equal(job_id) - job.should.have.key('job').which.should.have.key("targets") - job.should.have.key('job').which.should.have.key("jobProcessDetails") - job.should.have.key('job').which.should.have.key("lastUpdatedAt") - job.should.have.key('job').which.should.have.key("createdAt") - job.should.have.key('job').which.should.have.key("jobExecutionsRolloutConfig") - job.should.have.key('job').which.should.have.key("targetSelection").which.should.equal("CONTINUOUS") - job.should.have.key('job').which.should.have.key("presignedUrlConfig") - job.should.have.key('job').which.should.have.key("presignedUrlConfig").which.should.have.key( - "roleArn").which.should.equal('arn:aws:iam::1:role/service-role/iot_job_role') - job.should.have.key('job').which.should.have.key("presignedUrlConfig").which.should.have.key( - "expiresInSec").which.should.equal(123) - job.should.have.key('job').which.should.have.key("jobExecutionsRolloutConfig").which.should.have.key( - "maximumPerMinute").which.should.equal(10) - - -@mock_iot -def test_describe_job_1(): - 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), - 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 = client.describe_job(jobId=job_id) - job.should.have.key('job') - job.should.have.key('job').which.should.have.key("jobArn") - job.should.have.key('job').which.should.have.key("jobId").which.should.equal(job_id) - job.should.have.key('job').which.should.have.key("targets") - job.should.have.key('job').which.should.have.key("jobProcessDetails") - job.should.have.key('job').which.should.have.key("lastUpdatedAt") - job.should.have.key('job').which.should.have.key("createdAt") - job.should.have.key('job').which.should.have.key("jobExecutionsRolloutConfig") - job.should.have.key('job').which.should.have.key("targetSelection").which.should.equal("CONTINUOUS") - job.should.have.key('job').which.should.have.key("presignedUrlConfig") - job.should.have.key('job').which.should.have.key("presignedUrlConfig").which.should.have.key( - "roleArn").which.should.equal('arn:aws:iam::1:role/service-role/iot_job_role') - job.should.have.key('job').which.should.have.key("presignedUrlConfig").which.should.have.key( - "expiresInSec").which.should.equal(123) - job.should.have.key('job').which.should.have.key("jobExecutionsRolloutConfig").which.should.have.key( - "maximumPerMinute").which.should.equal(10) - - -@mock_iot -def test_delete_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 = client.create_job( - jobId=job_id, - targets=[thing["thingArn"]], - documentSource="https://s3-eu-west-1.amazonaws.com/bucket-name/job_document.json", - 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 = client.describe_job(jobId=job_id) - job.should.have.key('job') - job.should.have.key('job').which.should.have.key("jobId").which.should.equal(job_id) - - client.delete_job(jobId=job_id) - - client.list_jobs()['jobs'].should.have.length_of(0) - - -@mock_iot -def test_cancel_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 = client.create_job( - jobId=job_id, - targets=[thing["thingArn"]], - documentSource="https://s3-eu-west-1.amazonaws.com/bucket-name/job_document.json", - 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 = client.describe_job(jobId=job_id) - job.should.have.key('job') - job.should.have.key('job').which.should.have.key("jobId").which.should.equal(job_id) - - job = client.cancel_job(jobId=job_id, reasonCode='Because', comment='You are') - job.should.have.key('jobId').which.should.equal(job_id) - job.should.have.key('jobArn') - - job = client.describe_job(jobId=job_id) - job.should.have.key('job') - job.should.have.key('job').which.should.have.key("jobId").which.should.equal(job_id) - job.should.have.key('job').which.should.have.key("status").which.should.equal('CANCELED') - job.should.have.key('job').which.should.have.key("forceCanceled").which.should.equal(False) - job.should.have.key('job').which.should.have.key("reasonCode").which.should.equal('Because') - job.should.have.key('job').which.should.have.key("comment").which.should.equal('You are') - - -@mock_iot -def test_get_job_document_with_document_source(): - 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 = client.create_job( - jobId=job_id, - targets=[thing["thingArn"]], - documentSource="https://s3-eu-west-1.amazonaws.com/bucket-name/job_document.json", - 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_document = client.get_job_document(jobId=job_id) - job_document.should.have.key('document').which.should.equal('') - - -@mock_iot -def test_get_job_document_with_document(): - 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), - 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_document = client.get_job_document(jobId=job_id) - job_document.should.have.key('document').which.should.equal("{\"field\": \"value\"}") - - -@mock_iot -def test_describe_job_execution(): - 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.describe_job_execution(jobId=job_id, thingName=name) - job_execution.should.have.key('execution') - job_execution['execution'].should.have.key('jobId').which.should.equal(job_id) - job_execution['execution'].should.have.key('status').which.should.equal('QUEUED') - job_execution['execution'].should.have.key('forceCanceled').which.should.equal(False) - job_execution['execution'].should.have.key('statusDetails').which.should.equal({'detailsMap': {}}) - job_execution['execution'].should.have.key('thingArn').which.should.equal(thing["thingArn"]) - job_execution['execution'].should.have.key('queuedAt') - job_execution['execution'].should.have.key('startedAt') - job_execution['execution'].should.have.key('lastUpdatedAt') - job_execution['execution'].should.have.key('executionNumber').which.should.equal(123) - job_execution['execution'].should.have.key('versionNumber').which.should.equal(123) - job_execution['execution'].should.have.key('approximateSecondsBeforeTimedOut').which.should.equal(123) - - job_execution = client.describe_job_execution(jobId=job_id, thingName=name, executionNumber=123) - job_execution.should.have.key('execution') - job_execution['execution'].should.have.key('jobId').which.should.equal(job_id) - job_execution['execution'].should.have.key('status').which.should.equal('QUEUED') - job_execution['execution'].should.have.key('forceCanceled').which.should.equal(False) - job_execution['execution'].should.have.key('statusDetails').which.should.equal({'detailsMap': {}}) - job_execution['execution'].should.have.key('thingArn').which.should.equal(thing["thingArn"]) - job_execution['execution'].should.have.key('queuedAt') - job_execution['execution'].should.have.key('startedAt') - job_execution['execution'].should.have.key('lastUpdatedAt') - job_execution['execution'].should.have.key('executionNumber').which.should.equal(123) - job_execution['execution'].should.have.key('versionNumber').which.should.equal(123) - job_execution['execution'].should.have.key('approximateSecondsBeforeTimedOut').which.should.equal(123) - - try: - client.describe_job_execution(jobId=job_id, thingName=name, executionNumber=456) - except ClientError as exc: - error_code = exc.response['Error']['Code'] - error_code.should.equal('ResourceNotFoundException') - else: - raise Exception("Should have raised error") - - -@mock_iot -def test_cancel_job_execution(): - 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') - - client.cancel_job_execution(jobId=job_id, thingName=name) - job_execution = client.describe_job_execution(jobId=job_id, thingName=name) - job_execution.should.have.key('execution') - job_execution['execution'].should.have.key('status').which.should.equal('CANCELED') - - -@mock_iot -def test_delete_job_execution(): - 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') - - client.delete_job_execution(jobId=job_id, thingName=name, executionNumber=123) - try: - client.describe_job_execution(jobId=job_id, thingName=name, executionNumber=123) - except ClientError as exc: - error_code = exc.response['Error']['Code'] - error_code.should.equal('ResourceNotFoundException') - else: - raise Exception("Should have raised error") - - -@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('thingArn').which.should.equal(thing["thingArn"]) - - -@mock_iot -def test_list_job_executions_for_thing(): - 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_thing(thingName=name) - job_execution.should.have.key('executionSummaries') - job_execution['executionSummaries'][0].should.have.key('jobId').which.should.equal(job_id) - +from __future__ import unicode_literals + +import json +import sure #noqa +import boto3 + +from moto import mock_iot +from botocore.exceptions import ClientError +from nose.tools import assert_raises + +@mock_iot +def test_attach_policy(): + client = boto3.client('iot', region_name='ap-northeast-1') + policy_name = 'my-policy' + doc = '{}' + + cert = client.create_keys_and_certificate(setAsActive=True) + cert_arn = cert['certificateArn'] + client.create_policy(policyName=policy_name, policyDocument=doc) + client.attach_policy(policyName=policy_name, target=cert_arn) + + res = client.list_attached_policies(target=cert_arn) + res.should.have.key('policies').which.should.have.length_of(1) + res['policies'][0]['policyName'].should.equal('my-policy') + + +@mock_iot +def test_detach_policy(): + client = boto3.client('iot', region_name='ap-northeast-1') + policy_name = 'my-policy' + doc = '{}' + + cert = client.create_keys_and_certificate(setAsActive=True) + cert_arn = cert['certificateArn'] + client.create_policy(policyName=policy_name, policyDocument=doc) + client.attach_policy(policyName=policy_name, target=cert_arn) + + res = client.list_attached_policies(target=cert_arn) + res.should.have.key('policies').which.should.have.length_of(1) + res['policies'][0]['policyName'].should.equal('my-policy') + + client.detach_policy(policyName=policy_name, target=cert_arn) + res = client.list_attached_policies(target=cert_arn) + res.should.have.key('policies').which.should.be.empty + + +@mock_iot +def test_list_attached_policies(): + client = boto3.client('iot', region_name='ap-northeast-1') + cert = client.create_keys_and_certificate(setAsActive=True) + policies = client.list_attached_policies(target=cert['certificateArn']) + policies['policies'].should.be.empty + + +@mock_iot +def test_policy_versions(): + client = boto3.client('iot', region_name='ap-northeast-1') + policy_name = 'my-policy' + doc = '{}' + + policy = client.create_policy(policyName=policy_name, policyDocument=doc) + policy.should.have.key('policyName').which.should.equal(policy_name) + policy.should.have.key('policyArn').which.should_not.be.none + policy.should.have.key('policyDocument').which.should.equal(json.dumps({})) + policy.should.have.key('policyVersionId').which.should.equal('1') + + policy = client.get_policy(policyName=policy_name) + policy.should.have.key('policyName').which.should.equal(policy_name) + policy.should.have.key('policyArn').which.should_not.be.none + policy.should.have.key('policyDocument').which.should.equal(json.dumps({})) + policy.should.have.key('defaultVersionId').which.should.equal(policy['defaultVersionId']) + + policy1 = client.create_policy_version(policyName=policy_name, policyDocument=json.dumps({'version': 'version_1'}), + setAsDefault=True) + policy1.should.have.key('policyArn').which.should_not.be.none + policy1.should.have.key('policyDocument').which.should.equal(json.dumps({'version': 'version_1'})) + policy1.should.have.key('policyVersionId').which.should.equal('2') + policy1.should.have.key('isDefaultVersion').which.should.equal(True) + + policy2 = client.create_policy_version(policyName=policy_name, policyDocument=json.dumps({'version': 'version_2'}), + setAsDefault=False) + policy2.should.have.key('policyArn').which.should_not.be.none + policy2.should.have.key('policyDocument').which.should.equal(json.dumps({'version': 'version_2'})) + policy2.should.have.key('policyVersionId').which.should.equal('3') + policy2.should.have.key('isDefaultVersion').which.should.equal(False) + + policy = client.get_policy(policyName=policy_name) + policy.should.have.key('policyName').which.should.equal(policy_name) + policy.should.have.key('policyArn').which.should_not.be.none + policy.should.have.key('policyDocument').which.should.equal(json.dumps({'version': 'version_1'})) + policy.should.have.key('defaultVersionId').which.should.equal(policy1['policyVersionId']) + + policy_versions = client.list_policy_versions(policyName=policy_name) + policy_versions.should.have.key('policyVersions').which.should.have.length_of(3) + list(map(lambda item: item['isDefaultVersion'], policy_versions['policyVersions'])).count(True).should.equal(1) + default_policy = list(filter(lambda item: item['isDefaultVersion'], policy_versions['policyVersions'])) + default_policy[0].should.have.key('versionId').should.equal(policy1['policyVersionId']) + + policy = client.get_policy(policyName=policy_name) + policy.should.have.key('policyName').which.should.equal(policy_name) + policy.should.have.key('policyArn').which.should_not.be.none + policy.should.have.key('policyDocument').which.should.equal(json.dumps({'version': 'version_1'})) + policy.should.have.key('defaultVersionId').which.should.equal(policy1['policyVersionId']) + + client.set_default_policy_version(policyName=policy_name, policyVersionId=policy2['policyVersionId']) + policy_versions = client.list_policy_versions(policyName=policy_name) + policy_versions.should.have.key('policyVersions').which.should.have.length_of(3) + list(map(lambda item: item['isDefaultVersion'], policy_versions['policyVersions'])).count(True).should.equal(1) + default_policy = list(filter(lambda item: item['isDefaultVersion'], policy_versions['policyVersions'])) + default_policy[0].should.have.key('versionId').should.equal(policy2['policyVersionId']) + + policy = client.get_policy(policyName=policy_name) + policy.should.have.key('policyName').which.should.equal(policy_name) + policy.should.have.key('policyArn').which.should_not.be.none + policy.should.have.key('policyDocument').which.should.equal(json.dumps({'version': 'version_2'})) + policy.should.have.key('defaultVersionId').which.should.equal(policy2['policyVersionId']) + + client.delete_policy_version(policyName=policy_name, policyVersionId='1') + policy_versions = client.list_policy_versions(policyName=policy_name) + policy_versions.should.have.key('policyVersions').which.should.have.length_of(2) + + client.delete_policy_version(policyName=policy_name, policyVersionId=policy1['policyVersionId']) + policy_versions = client.list_policy_versions(policyName=policy_name) + policy_versions.should.have.key('policyVersions').which.should.have.length_of(1) + + # should fail as it's the default policy. Should use delete_policy instead + try: + client.delete_policy_version(policyName=policy_name, policyVersionId=policy2['policyVersionId']) + assert False, 'Should have failed in previous call' + except Exception as exception: + exception.response['Error']['Message'].should.equal('Cannot delete the default version of a policy') + + +@mock_iot +def test_things(): + client = boto3.client('iot', region_name='ap-northeast-1') + name = 'my-thing' + type_name = 'my-type-name' + + # thing type + thing_type = client.create_thing_type(thingTypeName=type_name) + thing_type.should.have.key('thingTypeName').which.should.equal(type_name) + thing_type.should.have.key('thingTypeArn') + + res = client.list_thing_types() + res.should.have.key('thingTypes').which.should.have.length_of(1) + for thing_type in res['thingTypes']: + thing_type.should.have.key('thingTypeName').which.should_not.be.none + + thing_type = client.describe_thing_type(thingTypeName=type_name) + thing_type.should.have.key('thingTypeName').which.should.equal(type_name) + thing_type.should.have.key('thingTypeProperties') + thing_type.should.have.key('thingTypeMetadata') + + # thing + thing = client.create_thing(thingName=name, thingTypeName=type_name) + thing.should.have.key('thingName').which.should.equal(name) + thing.should.have.key('thingArn') + res = client.list_things() + res.should.have.key('things').which.should.have.length_of(1) + for thing in res['things']: + thing.should.have.key('thingName').which.should_not.be.none + thing.should.have.key('thingArn').which.should_not.be.none + + thing = client.update_thing(thingName=name, attributePayload={'attributes': {'k1': 'v1'}}) + res = client.list_things() + res.should.have.key('things').which.should.have.length_of(1) + for thing in res['things']: + thing.should.have.key('thingName').which.should_not.be.none + thing.should.have.key('thingArn').which.should_not.be.none + res['things'][0]['attributes'].should.have.key('k1').which.should.equal('v1') + + thing = client.describe_thing(thingName=name) + thing.should.have.key('thingName').which.should.equal(name) + thing.should.have.key('defaultClientId') + thing.should.have.key('thingTypeName') + thing.should.have.key('attributes') + thing.should.have.key('version') + + # delete thing + client.delete_thing(thingName=name) + res = client.list_things() + res.should.have.key('things').which.should.have.length_of(0) + + # delete thing type + client.delete_thing_type(thingTypeName=type_name) + res = client.list_thing_types() + res.should.have.key('thingTypes').which.should.have.length_of(0) + + +@mock_iot +def test_list_thing_types(): + client = boto3.client('iot', region_name='ap-northeast-1') + + for i in range(0, 100): + client.create_thing_type(thingTypeName=str(i + 1)) + + thing_types = client.list_thing_types() + thing_types.should.have.key('nextToken') + thing_types.should.have.key('thingTypes').which.should.have.length_of(50) + thing_types['thingTypes'][0]['thingTypeName'].should.equal('1') + thing_types['thingTypes'][-1]['thingTypeName'].should.equal('50') + + thing_types = client.list_thing_types(nextToken=thing_types['nextToken']) + thing_types.should.have.key('thingTypes').which.should.have.length_of(50) + thing_types.should_not.have.key('nextToken') + thing_types['thingTypes'][0]['thingTypeName'].should.equal('51') + thing_types['thingTypes'][-1]['thingTypeName'].should.equal('100') + + +@mock_iot +def test_list_thing_types_with_typename_filter(): + client = boto3.client('iot', region_name='ap-northeast-1') + + client.create_thing_type(thingTypeName='thing') + client.create_thing_type(thingTypeName='thingType') + client.create_thing_type(thingTypeName='thingTypeName') + client.create_thing_type(thingTypeName='thingTypeNameGroup') + client.create_thing_type(thingTypeName='shouldNotFind') + client.create_thing_type(thingTypeName='find me it shall not') + + thing_types = client.list_thing_types(thingTypeName='thing') + thing_types.should_not.have.key('nextToken') + thing_types.should.have.key('thingTypes').which.should.have.length_of(4) + thing_types['thingTypes'][0]['thingTypeName'].should.equal('thing') + thing_types['thingTypes'][-1]['thingTypeName'].should.equal('thingTypeNameGroup') + + thing_types = client.list_thing_types(thingTypeName='thingTypeName') + thing_types.should_not.have.key('nextToken') + thing_types.should.have.key('thingTypes').which.should.have.length_of(2) + thing_types['thingTypes'][0]['thingTypeName'].should.equal('thingTypeName') + thing_types['thingTypes'][-1]['thingTypeName'].should.equal('thingTypeNameGroup') + + +@mock_iot +def test_list_things_with_next_token(): + client = boto3.client('iot', region_name='ap-northeast-1') + + for i in range(0, 200): + client.create_thing(thingName=str(i + 1)) + + things = client.list_things() + things.should.have.key('nextToken') + things.should.have.key('things').which.should.have.length_of(50) + things['things'][0]['thingName'].should.equal('1') + things['things'][0]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/1') + things['things'][-1]['thingName'].should.equal('50') + things['things'][-1]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/50') + + things = client.list_things(nextToken=things['nextToken']) + things.should.have.key('nextToken') + things.should.have.key('things').which.should.have.length_of(50) + things['things'][0]['thingName'].should.equal('51') + things['things'][0]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/51') + things['things'][-1]['thingName'].should.equal('100') + things['things'][-1]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/100') + + things = client.list_things(nextToken=things['nextToken']) + things.should.have.key('nextToken') + things.should.have.key('things').which.should.have.length_of(50) + things['things'][0]['thingName'].should.equal('101') + things['things'][0]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/101') + things['things'][-1]['thingName'].should.equal('150') + things['things'][-1]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/150') + + things = client.list_things(nextToken=things['nextToken']) + things.should_not.have.key('nextToken') + things.should.have.key('things').which.should.have.length_of(50) + things['things'][0]['thingName'].should.equal('151') + things['things'][0]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/151') + things['things'][-1]['thingName'].should.equal('200') + things['things'][-1]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/200') + + +@mock_iot +def test_list_things_with_attribute_and_thing_type_filter_and_next_token(): + client = boto3.client('iot', region_name='ap-northeast-1') + client.create_thing_type(thingTypeName='my-thing-type') + + for i in range(0, 200): + if not (i + 1) % 3: + attribute_payload = { + 'attributes': { + 'foo': 'bar' + } + } + elif not (i + 1) % 5: + attribute_payload = { + 'attributes': { + 'bar': 'foo' + } + } + else: + attribute_payload = {} + + if not (i + 1) % 2: + thing_type_name = 'my-thing-type' + client.create_thing(thingName=str(i + 1), thingTypeName=thing_type_name, attributePayload=attribute_payload) + else: + client.create_thing(thingName=str(i + 1), attributePayload=attribute_payload) + + # Test filter for thingTypeName + things = client.list_things(thingTypeName=thing_type_name) + things.should.have.key('nextToken') + things.should.have.key('things').which.should.have.length_of(50) + things['things'][0]['thingName'].should.equal('2') + things['things'][0]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/2') + things['things'][-1]['thingName'].should.equal('100') + things['things'][-1]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/100') + all(item['thingTypeName'] == thing_type_name for item in things['things']) + + things = client.list_things(nextToken=things['nextToken'], thingTypeName=thing_type_name) + things.should_not.have.key('nextToken') + things.should.have.key('things').which.should.have.length_of(50) + things['things'][0]['thingName'].should.equal('102') + things['things'][0]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/102') + things['things'][-1]['thingName'].should.equal('200') + things['things'][-1]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/200') + all(item['thingTypeName'] == thing_type_name for item in things['things']) + + # Test filter for attributes + things = client.list_things(attributeName='foo', attributeValue='bar') + things.should.have.key('nextToken') + things.should.have.key('things').which.should.have.length_of(50) + things['things'][0]['thingName'].should.equal('3') + things['things'][0]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/3') + things['things'][-1]['thingName'].should.equal('150') + things['things'][-1]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/150') + all(item['attributes'] == {'foo': 'bar'} for item in things['things']) + + things = client.list_things(nextToken=things['nextToken'], attributeName='foo', attributeValue='bar') + things.should_not.have.key('nextToken') + things.should.have.key('things').which.should.have.length_of(16) + things['things'][0]['thingName'].should.equal('153') + things['things'][0]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/153') + things['things'][-1]['thingName'].should.equal('198') + things['things'][-1]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/198') + all(item['attributes'] == {'foo': 'bar'} for item in things['things']) + + # Test filter for attributes and thingTypeName + things = client.list_things(thingTypeName=thing_type_name, attributeName='foo', attributeValue='bar') + things.should_not.have.key('nextToken') + things.should.have.key('things').which.should.have.length_of(33) + things['things'][0]['thingName'].should.equal('6') + things['things'][0]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/6') + things['things'][-1]['thingName'].should.equal('198') + things['things'][-1]['thingArn'].should.equal('arn:aws:iot:ap-northeast-1:1:thing/198') + all(item['attributes'] == {'foo': 'bar'} and item['thingTypeName'] == thing_type_name for item in things['things']) + + +@mock_iot +def test_certs(): + client = boto3.client('iot', region_name='us-east-1') + cert = client.create_keys_and_certificate(setAsActive=True) + cert.should.have.key('certificateArn').which.should_not.be.none + cert.should.have.key('certificateId').which.should_not.be.none + cert.should.have.key('certificatePem').which.should_not.be.none + cert.should.have.key('keyPair') + cert['keyPair'].should.have.key('PublicKey').which.should_not.be.none + cert['keyPair'].should.have.key('PrivateKey').which.should_not.be.none + cert_id = cert['certificateId'] + + cert = client.describe_certificate(certificateId=cert_id) + cert.should.have.key('certificateDescription') + cert_desc = cert['certificateDescription'] + cert_desc.should.have.key('certificateArn').which.should_not.be.none + cert_desc.should.have.key('certificateId').which.should_not.be.none + cert_desc.should.have.key('certificatePem').which.should_not.be.none + cert_desc.should.have.key('status').which.should.equal('ACTIVE') + cert_pem = cert_desc['certificatePem'] + + res = client.list_certificates() + for cert in res['certificates']: + cert.should.have.key('certificateArn').which.should_not.be.none + cert.should.have.key('certificateId').which.should_not.be.none + cert.should.have.key('status').which.should_not.be.none + cert.should.have.key('creationDate').which.should_not.be.none + + client.update_certificate(certificateId=cert_id, newStatus='REVOKED') + cert = client.describe_certificate(certificateId=cert_id) + cert_desc = cert['certificateDescription'] + cert_desc.should.have.key('status').which.should.equal('REVOKED') + + client.delete_certificate(certificateId=cert_id) + res = client.list_certificates() + res.should.have.key('certificates') + + # Test register_certificate flow + cert = client.register_certificate(certificatePem=cert_pem, setAsActive=True) + cert.should.have.key('certificateId').which.should_not.be.none + cert.should.have.key('certificateArn').which.should_not.be.none + cert_id = cert['certificateId'] + + res = client.list_certificates() + res.should.have.key('certificates').which.should.have.length_of(1) + for cert in res['certificates']: + cert.should.have.key('certificateArn').which.should_not.be.none + cert.should.have.key('certificateId').which.should_not.be.none + cert.should.have.key('status').which.should_not.be.none + cert.should.have.key('creationDate').which.should_not.be.none + + client.update_certificate(certificateId=cert_id, newStatus='REVOKED') + cert = client.describe_certificate(certificateId=cert_id) + cert_desc = cert['certificateDescription'] + cert_desc.should.have.key('status').which.should.equal('REVOKED') + + client.delete_certificate(certificateId=cert_id) + res = client.list_certificates() + res.should.have.key('certificates') + + +@mock_iot +def test_delete_policy_validation(): + doc = """{ + "Version": "2012-10-17", + "Statement":[ + { + "Effect":"Allow", + "Action":[ + "iot: *" + ], + "Resource":"*" + } + ] + } + """ + client = boto3.client('iot', region_name='ap-northeast-1') + cert = client.create_keys_and_certificate(setAsActive=True) + cert_arn = cert['certificateArn'] + policy_name = 'my-policy' + client.create_policy(policyName=policy_name, policyDocument=doc) + client.attach_principal_policy(policyName=policy_name, principal=cert_arn) + + with assert_raises(ClientError) as e: + client.delete_policy(policyName=policy_name) + e.exception.response['Error']['Message'].should.contain( + 'The policy cannot be deleted as the policy is attached to one or more principals (name=%s)' % policy_name) + res = client.list_policies() + res.should.have.key('policies').which.should.have.length_of(1) + + client.detach_principal_policy(policyName=policy_name, principal=cert_arn) + client.delete_policy(policyName=policy_name) + res = client.list_policies() + res.should.have.key('policies').which.should.have.length_of(0) + + +@mock_iot +def test_delete_certificate_validation(): + doc = """{ + "Version": "2012-10-17", + "Statement":[ + { + "Effect":"Allow", + "Action":[ + "iot: *" + ], + "Resource":"*" + } + ] + } + """ + client = boto3.client('iot', region_name='ap-northeast-1') + cert = client.create_keys_and_certificate(setAsActive=True) + cert_id = cert['certificateId'] + cert_arn = cert['certificateArn'] + policy_name = 'my-policy' + thing_name = 'thing-1' + client.create_policy(policyName=policy_name, policyDocument=doc) + client.attach_principal_policy(policyName=policy_name, principal=cert_arn) + client.create_thing(thingName=thing_name) + client.attach_thing_principal(thingName=thing_name, principal=cert_arn) + + with assert_raises(ClientError) as e: + client.delete_certificate(certificateId=cert_id) + e.exception.response['Error']['Message'].should.contain( + 'Certificate must be deactivated (not ACTIVE) before deletion.') + res = client.list_certificates() + res.should.have.key('certificates').which.should.have.length_of(1) + + client.update_certificate(certificateId=cert_id, newStatus='REVOKED') + with assert_raises(ClientError) as e: + client.delete_certificate(certificateId=cert_id) + e.exception.response['Error']['Message'].should.contain( + 'Things must be detached before deletion (arn: %s)' % cert_arn) + res = client.list_certificates() + res.should.have.key('certificates').which.should.have.length_of(1) + + client.detach_thing_principal(thingName=thing_name, principal=cert_arn) + with assert_raises(ClientError) as e: + client.delete_certificate(certificateId=cert_id) + e.exception.response['Error']['Message'].should.contain( + 'Certificate policies must be detached before deletion (arn: %s)' % cert_arn) + res = client.list_certificates() + res.should.have.key('certificates').which.should.have.length_of(1) + + client.detach_principal_policy(policyName=policy_name, principal=cert_arn) + client.delete_certificate(certificateId=cert_id) + res = client.list_certificates() + res.should.have.key('certificates').which.should.have.length_of(0) + + +@mock_iot +def test_certs_create_inactive(): + client = boto3.client('iot', region_name='ap-northeast-1') + cert = client.create_keys_and_certificate(setAsActive=False) + cert_id = cert['certificateId'] + + cert = client.describe_certificate(certificateId=cert_id) + cert.should.have.key('certificateDescription') + cert_desc = cert['certificateDescription'] + cert_desc.should.have.key('status').which.should.equal('INACTIVE') + + client.update_certificate(certificateId=cert_id, newStatus='ACTIVE') + cert = client.describe_certificate(certificateId=cert_id) + cert.should.have.key('certificateDescription') + cert_desc = cert['certificateDescription'] + cert_desc.should.have.key('status').which.should.equal('ACTIVE') + + +@mock_iot +def test_policy(): + client = boto3.client('iot', region_name='ap-northeast-1') + name = 'my-policy' + doc = '{}' + policy = client.create_policy(policyName=name, policyDocument=doc) + policy.should.have.key('policyName').which.should.equal(name) + policy.should.have.key('policyArn').which.should_not.be.none + policy.should.have.key('policyDocument').which.should.equal(doc) + policy.should.have.key('policyVersionId').which.should.equal('1') + + policy = client.get_policy(policyName=name) + policy.should.have.key('policyName').which.should.equal(name) + policy.should.have.key('policyArn').which.should_not.be.none + policy.should.have.key('policyDocument').which.should.equal(doc) + policy.should.have.key('defaultVersionId').which.should.equal('1') + + res = client.list_policies() + res.should.have.key('policies').which.should.have.length_of(1) + for policy in res['policies']: + policy.should.have.key('policyName').which.should_not.be.none + policy.should.have.key('policyArn').which.should_not.be.none + + client.delete_policy(policyName=name) + res = client.list_policies() + res.should.have.key('policies').which.should.have.length_of(0) + + +@mock_iot +def test_principal_policy(): + client = boto3.client('iot', region_name='ap-northeast-1') + policy_name = 'my-policy' + doc = '{}' + client.create_policy(policyName=policy_name, policyDocument=doc) + cert = client.create_keys_and_certificate(setAsActive=True) + cert_arn = cert['certificateArn'] + + client.attach_policy(policyName=policy_name, target=cert_arn) + + res = client.list_principal_policies(principal=cert_arn) + res.should.have.key('policies').which.should.have.length_of(1) + for policy in res['policies']: + policy.should.have.key('policyName').which.should_not.be.none + policy.should.have.key('policyArn').which.should_not.be.none + + # do nothing if policy have already attached to certificate + client.attach_policy(policyName=policy_name, target=cert_arn) + + res = client.list_principal_policies(principal=cert_arn) + res.should.have.key('policies').which.should.have.length_of(1) + for policy in res['policies']: + policy.should.have.key('policyName').which.should_not.be.none + policy.should.have.key('policyArn').which.should_not.be.none + + res = client.list_policy_principals(policyName=policy_name) + res.should.have.key('principals').which.should.have.length_of(1) + for principal in res['principals']: + principal.should_not.be.none + + client.detach_policy(policyName=policy_name, target=cert_arn) + res = client.list_principal_policies(principal=cert_arn) + res.should.have.key('policies').which.should.have.length_of(0) + res = client.list_policy_principals(policyName=policy_name) + res.should.have.key('principals').which.should.have.length_of(0) + with assert_raises(ClientError) as e: + client.detach_policy(policyName=policy_name, target=cert_arn) + e.exception.response['Error']['Code'].should.equal('ResourceNotFoundException') + + +@mock_iot +def test_principal_policy_deprecated(): + client = boto3.client('iot', region_name='ap-northeast-1') + policy_name = 'my-policy' + doc = '{}' + policy = client.create_policy(policyName=policy_name, policyDocument=doc) + cert = client.create_keys_and_certificate(setAsActive=True) + cert_arn = cert['certificateArn'] + + client.attach_principal_policy(policyName=policy_name, principal=cert_arn) + + res = client.list_principal_policies(principal=cert_arn) + res.should.have.key('policies').which.should.have.length_of(1) + for policy in res['policies']: + policy.should.have.key('policyName').which.should_not.be.none + policy.should.have.key('policyArn').which.should_not.be.none + + res = client.list_policy_principals(policyName=policy_name) + res.should.have.key('principals').which.should.have.length_of(1) + for principal in res['principals']: + principal.should_not.be.none + + client.detach_principal_policy(policyName=policy_name, principal=cert_arn) + res = client.list_principal_policies(principal=cert_arn) + res.should.have.key('policies').which.should.have.length_of(0) + res = client.list_policy_principals(policyName=policy_name) + res.should.have.key('principals').which.should.have.length_of(0) + + +@mock_iot +def test_principal_thing(): + client = boto3.client('iot', region_name='ap-northeast-1') + thing_name = 'my-thing' + thing = client.create_thing(thingName=thing_name) + cert = client.create_keys_and_certificate(setAsActive=True) + cert_arn = cert['certificateArn'] + + client.attach_thing_principal(thingName=thing_name, principal=cert_arn) + + res = client.list_principal_things(principal=cert_arn) + res.should.have.key('things').which.should.have.length_of(1) + for thing in res['things']: + thing.should_not.be.none + res = client.list_thing_principals(thingName=thing_name) + res.should.have.key('principals').which.should.have.length_of(1) + for principal in res['principals']: + principal.should_not.be.none + + client.detach_thing_principal(thingName=thing_name, principal=cert_arn) + res = client.list_principal_things(principal=cert_arn) + res.should.have.key('things').which.should.have.length_of(0) + res = client.list_thing_principals(thingName=thing_name) + res.should.have.key('principals').which.should.have.length_of(0) + + +@mock_iot +def test_thing_groups(): + client = boto3.client('iot', region_name='ap-northeast-1') + group_name = 'my-group-name' + + # thing group + thing_group = client.create_thing_group(thingGroupName=group_name) + thing_group.should.have.key('thingGroupName').which.should.equal(group_name) + thing_group.should.have.key('thingGroupArn') + + res = client.list_thing_groups() + res.should.have.key('thingGroups').which.should.have.length_of(1) + for thing_group in res['thingGroups']: + thing_group.should.have.key('groupName').which.should_not.be.none + thing_group.should.have.key('groupArn').which.should_not.be.none + + thing_group = client.describe_thing_group(thingGroupName=group_name) + thing_group.should.have.key('thingGroupName').which.should.equal(group_name) + thing_group.should.have.key('thingGroupProperties') + thing_group.should.have.key('thingGroupMetadata') + thing_group.should.have.key('version') + + # delete thing group + client.delete_thing_group(thingGroupName=group_name) + res = client.list_thing_groups() + res.should.have.key('thingGroups').which.should.have.length_of(0) + + # props create test + props = { + 'thingGroupDescription': 'my first thing group', + 'attributePayload': { + 'attributes': { + 'key1': 'val01', + 'Key02': 'VAL2' + } + } + } + thing_group = client.create_thing_group(thingGroupName=group_name, thingGroupProperties=props) + thing_group.should.have.key('thingGroupName').which.should.equal(group_name) + thing_group.should.have.key('thingGroupArn') + + thing_group = client.describe_thing_group(thingGroupName=group_name) + thing_group.should.have.key('thingGroupProperties') \ + .which.should.have.key('attributePayload') \ + .which.should.have.key('attributes') + res_props = thing_group['thingGroupProperties']['attributePayload']['attributes'] + res_props.should.have.key('key1').which.should.equal('val01') + res_props.should.have.key('Key02').which.should.equal('VAL2') + + # props update test with merge + new_props = { + 'attributePayload': { + 'attributes': { + 'k3': 'v3' + }, + 'merge': True + } + } + client.update_thing_group( + thingGroupName=group_name, + thingGroupProperties=new_props + ) + thing_group = client.describe_thing_group(thingGroupName=group_name) + thing_group.should.have.key('thingGroupProperties') \ + .which.should.have.key('attributePayload') \ + .which.should.have.key('attributes') + res_props = thing_group['thingGroupProperties']['attributePayload']['attributes'] + res_props.should.have.key('key1').which.should.equal('val01') + res_props.should.have.key('Key02').which.should.equal('VAL2') + + res_props.should.have.key('k3').which.should.equal('v3') + + # props update test + new_props = { + 'attributePayload': { + 'attributes': { + 'k4': 'v4' + } + } + } + client.update_thing_group( + thingGroupName=group_name, + thingGroupProperties=new_props + ) + thing_group = client.describe_thing_group(thingGroupName=group_name) + thing_group.should.have.key('thingGroupProperties') \ + .which.should.have.key('attributePayload') \ + .which.should.have.key('attributes') + res_props = thing_group['thingGroupProperties']['attributePayload']['attributes'] + res_props.should.have.key('k4').which.should.equal('v4') + res_props.should_not.have.key('key1') + + +@mock_iot +def test_thing_group_relations(): + client = boto3.client('iot', region_name='ap-northeast-1') + name = 'my-thing' + group_name = 'my-group-name' + + # thing group + thing_group = client.create_thing_group(thingGroupName=group_name) + thing_group.should.have.key('thingGroupName').which.should.equal(group_name) + thing_group.should.have.key('thingGroupArn') + + # thing + thing = client.create_thing(thingName=name) + thing.should.have.key('thingName').which.should.equal(name) + thing.should.have.key('thingArn') + + # add in 4 way + client.add_thing_to_thing_group( + thingGroupName=group_name, + thingName=name + ) + client.add_thing_to_thing_group( + thingGroupArn=thing_group['thingGroupArn'], + thingArn=thing['thingArn'] + ) + client.add_thing_to_thing_group( + thingGroupName=group_name, + thingArn=thing['thingArn'] + ) + client.add_thing_to_thing_group( + thingGroupArn=thing_group['thingGroupArn'], + thingName=name + ) + + things = client.list_things_in_thing_group( + thingGroupName=group_name + ) + things.should.have.key('things') + things['things'].should.have.length_of(1) + + thing_groups = client.list_thing_groups_for_thing( + thingName=name + ) + thing_groups.should.have.key('thingGroups') + thing_groups['thingGroups'].should.have.length_of(1) + + # remove in 4 way + client.remove_thing_from_thing_group( + thingGroupName=group_name, + thingName=name + ) + client.remove_thing_from_thing_group( + thingGroupArn=thing_group['thingGroupArn'], + thingArn=thing['thingArn'] + ) + client.remove_thing_from_thing_group( + thingGroupName=group_name, + thingArn=thing['thingArn'] + ) + client.remove_thing_from_thing_group( + thingGroupArn=thing_group['thingGroupArn'], + thingName=name + ) + things = client.list_things_in_thing_group( + thingGroupName=group_name + ) + things.should.have.key('things') + things['things'].should.have.length_of(0) + + # update thing group for thing + client.update_thing_groups_for_thing( + thingName=name, + thingGroupsToAdd=[ + group_name + ] + ) + things = client.list_things_in_thing_group( + thingGroupName=group_name + ) + things.should.have.key('things') + things['things'].should.have.length_of(1) + + client.update_thing_groups_for_thing( + thingName=name, + thingGroupsToRemove=[ + group_name + ] + ) + things = client.list_things_in_thing_group( + thingGroupName=group_name + ) + things.should.have.key('things') + things['things'].should.have.length_of(0) + + +@mock_iot +def test_create_job(): + client = boto3.client('iot', region_name='eu-west-1') + name = "my-thing" + job_id = "TestJob" + # thing# job document + # job_document = { + # "field": "value" + # } + 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') + + +@mock_iot +def test_list_jobs(): + client = boto3.client('iot', region_name='eu-west-1') + name = "my-thing" + job_id = "TestJob" + # thing# job document + # job_document = { + # "field": "value" + # } + 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" + } + + job1 = 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 + } + ) + + job1.should.have.key('jobId').which.should.equal(job_id) + job1.should.have.key('jobArn') + job1.should.have.key('description') + + job2 = client.create_job( + jobId=job_id+"1", + 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 + } + ) + + job2.should.have.key('jobId').which.should.equal(job_id+"1") + job2.should.have.key('jobArn') + job2.should.have.key('description') + + jobs = client.list_jobs() + jobs.should.have.key('jobs') + jobs.should_not.have.key('nextToken') + jobs['jobs'][0].should.have.key('jobId').which.should.equal(job_id) + jobs['jobs'][1].should.have.key('jobId').which.should.equal(job_id+"1") + + +@mock_iot +def test_describe_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 = client.create_job( + jobId=job_id, + targets=[thing["thingArn"]], + documentSource="https://s3-eu-west-1.amazonaws.com/bucket-name/job_document.json", + 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 = client.describe_job(jobId=job_id) + job.should.have.key('documentSource') + job.should.have.key('job') + job.should.have.key('job').which.should.have.key("jobArn") + job.should.have.key('job').which.should.have.key("jobId").which.should.equal(job_id) + job.should.have.key('job').which.should.have.key("targets") + job.should.have.key('job').which.should.have.key("jobProcessDetails") + job.should.have.key('job').which.should.have.key("lastUpdatedAt") + job.should.have.key('job').which.should.have.key("createdAt") + job.should.have.key('job').which.should.have.key("jobExecutionsRolloutConfig") + job.should.have.key('job').which.should.have.key("targetSelection").which.should.equal("CONTINUOUS") + job.should.have.key('job').which.should.have.key("presignedUrlConfig") + job.should.have.key('job').which.should.have.key("presignedUrlConfig").which.should.have.key( + "roleArn").which.should.equal('arn:aws:iam::1:role/service-role/iot_job_role') + job.should.have.key('job').which.should.have.key("presignedUrlConfig").which.should.have.key( + "expiresInSec").which.should.equal(123) + job.should.have.key('job').which.should.have.key("jobExecutionsRolloutConfig").which.should.have.key( + "maximumPerMinute").which.should.equal(10) + + +@mock_iot +def test_describe_job_1(): + 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), + 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 = client.describe_job(jobId=job_id) + job.should.have.key('job') + job.should.have.key('job').which.should.have.key("jobArn") + job.should.have.key('job').which.should.have.key("jobId").which.should.equal(job_id) + job.should.have.key('job').which.should.have.key("targets") + job.should.have.key('job').which.should.have.key("jobProcessDetails") + job.should.have.key('job').which.should.have.key("lastUpdatedAt") + job.should.have.key('job').which.should.have.key("createdAt") + job.should.have.key('job').which.should.have.key("jobExecutionsRolloutConfig") + job.should.have.key('job').which.should.have.key("targetSelection").which.should.equal("CONTINUOUS") + job.should.have.key('job').which.should.have.key("presignedUrlConfig") + job.should.have.key('job').which.should.have.key("presignedUrlConfig").which.should.have.key( + "roleArn").which.should.equal('arn:aws:iam::1:role/service-role/iot_job_role') + job.should.have.key('job').which.should.have.key("presignedUrlConfig").which.should.have.key( + "expiresInSec").which.should.equal(123) + job.should.have.key('job').which.should.have.key("jobExecutionsRolloutConfig").which.should.have.key( + "maximumPerMinute").which.should.equal(10) + + +@mock_iot +def test_delete_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 = client.create_job( + jobId=job_id, + targets=[thing["thingArn"]], + documentSource="https://s3-eu-west-1.amazonaws.com/bucket-name/job_document.json", + 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 = client.describe_job(jobId=job_id) + job.should.have.key('job') + job.should.have.key('job').which.should.have.key("jobId").which.should.equal(job_id) + + client.delete_job(jobId=job_id) + + client.list_jobs()['jobs'].should.have.length_of(0) + + +@mock_iot +def test_cancel_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 = client.create_job( + jobId=job_id, + targets=[thing["thingArn"]], + documentSource="https://s3-eu-west-1.amazonaws.com/bucket-name/job_document.json", + 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 = client.describe_job(jobId=job_id) + job.should.have.key('job') + job.should.have.key('job').which.should.have.key("jobId").which.should.equal(job_id) + + job = client.cancel_job(jobId=job_id, reasonCode='Because', comment='You are') + job.should.have.key('jobId').which.should.equal(job_id) + job.should.have.key('jobArn') + + job = client.describe_job(jobId=job_id) + job.should.have.key('job') + job.should.have.key('job').which.should.have.key("jobId").which.should.equal(job_id) + job.should.have.key('job').which.should.have.key("status").which.should.equal('CANCELED') + job.should.have.key('job').which.should.have.key("forceCanceled").which.should.equal(False) + job.should.have.key('job').which.should.have.key("reasonCode").which.should.equal('Because') + job.should.have.key('job').which.should.have.key("comment").which.should.equal('You are') + + +@mock_iot +def test_get_job_document_with_document_source(): + 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 = client.create_job( + jobId=job_id, + targets=[thing["thingArn"]], + documentSource="https://s3-eu-west-1.amazonaws.com/bucket-name/job_document.json", + 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_document = client.get_job_document(jobId=job_id) + job_document.should.have.key('document').which.should.equal('') + + +@mock_iot +def test_get_job_document_with_document(): + 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), + 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_document = client.get_job_document(jobId=job_id) + job_document.should.have.key('document').which.should.equal("{\"field\": \"value\"}") + + +@mock_iot +def test_describe_job_execution(): + 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.describe_job_execution(jobId=job_id, thingName=name) + job_execution.should.have.key('execution') + job_execution['execution'].should.have.key('jobId').which.should.equal(job_id) + job_execution['execution'].should.have.key('status').which.should.equal('QUEUED') + job_execution['execution'].should.have.key('forceCanceled').which.should.equal(False) + job_execution['execution'].should.have.key('statusDetails').which.should.equal({'detailsMap': {}}) + job_execution['execution'].should.have.key('thingArn').which.should.equal(thing["thingArn"]) + job_execution['execution'].should.have.key('queuedAt') + job_execution['execution'].should.have.key('startedAt') + job_execution['execution'].should.have.key('lastUpdatedAt') + job_execution['execution'].should.have.key('executionNumber').which.should.equal(123) + job_execution['execution'].should.have.key('versionNumber').which.should.equal(123) + job_execution['execution'].should.have.key('approximateSecondsBeforeTimedOut').which.should.equal(123) + + job_execution = client.describe_job_execution(jobId=job_id, thingName=name, executionNumber=123) + job_execution.should.have.key('execution') + job_execution['execution'].should.have.key('jobId').which.should.equal(job_id) + job_execution['execution'].should.have.key('status').which.should.equal('QUEUED') + job_execution['execution'].should.have.key('forceCanceled').which.should.equal(False) + job_execution['execution'].should.have.key('statusDetails').which.should.equal({'detailsMap': {}}) + job_execution['execution'].should.have.key('thingArn').which.should.equal(thing["thingArn"]) + job_execution['execution'].should.have.key('queuedAt') + job_execution['execution'].should.have.key('startedAt') + job_execution['execution'].should.have.key('lastUpdatedAt') + job_execution['execution'].should.have.key('executionNumber').which.should.equal(123) + job_execution['execution'].should.have.key('versionNumber').which.should.equal(123) + job_execution['execution'].should.have.key('approximateSecondsBeforeTimedOut').which.should.equal(123) + + try: + client.describe_job_execution(jobId=job_id, thingName=name, executionNumber=456) + except ClientError as exc: + error_code = exc.response['Error']['Code'] + error_code.should.equal('ResourceNotFoundException') + else: + raise Exception("Should have raised error") + + +@mock_iot +def test_cancel_job_execution(): + 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') + + client.cancel_job_execution(jobId=job_id, thingName=name) + job_execution = client.describe_job_execution(jobId=job_id, thingName=name) + job_execution.should.have.key('execution') + job_execution['execution'].should.have.key('status').which.should.equal('CANCELED') + + +@mock_iot +def test_delete_job_execution(): + 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') + + client.delete_job_execution(jobId=job_id, thingName=name, executionNumber=123) + try: + client.describe_job_execution(jobId=job_id, thingName=name, executionNumber=123) + except ClientError as exc: + error_code = exc.response['Error']['Code'] + error_code.should.equal('ResourceNotFoundException') + else: + raise Exception("Should have raised error") + + +@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('thingArn').which.should.equal(thing["thingArn"]) + + +@mock_iot +def test_list_job_executions_for_thing(): + 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_thing(thingName=name) + job_execution.should.have.key('executionSummaries') + job_execution['executionSummaries'][0].should.have.key('jobId').which.should.equal(job_id) +