Merge pull request #2173 from sthuber90/master
Extending IoT Policy and Jobs functionality
This commit is contained in:
commit
b252ab6675
@ -22,6 +22,15 @@ class InvalidRequestException(IoTClientError):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidStateTransitionException(IoTClientError):
|
||||||
|
def __init__(self, msg=None):
|
||||||
|
self.code = 409
|
||||||
|
super(InvalidStateTransitionException, self).__init__(
|
||||||
|
"InvalidStateTransitionException",
|
||||||
|
msg or "An attempt was made to change to an invalid state.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class VersionConflictException(IoTClientError):
|
class VersionConflictException(IoTClientError):
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
self.code = 409
|
self.code = 409
|
||||||
|
@ -17,6 +17,7 @@ from .exceptions import (
|
|||||||
DeleteConflictException,
|
DeleteConflictException,
|
||||||
ResourceNotFoundException,
|
ResourceNotFoundException,
|
||||||
InvalidRequestException,
|
InvalidRequestException,
|
||||||
|
InvalidStateTransitionException,
|
||||||
VersionConflictException,
|
VersionConflictException,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -29,7 +30,7 @@ class FakeThing(BaseModel):
|
|||||||
self.attributes = attributes
|
self.attributes = attributes
|
||||||
self.arn = "arn:aws:iot:%s:1:thing/%s" % (self.region_name, thing_name)
|
self.arn = "arn:aws:iot:%s:1:thing/%s" % (self.region_name, thing_name)
|
||||||
self.version = 1
|
self.version = 1
|
||||||
# TODO: we need to handle 'version'?
|
# TODO: we need to handle "version"?
|
||||||
|
|
||||||
# for iot-data
|
# for iot-data
|
||||||
self.thing_shadow = None
|
self.thing_shadow = None
|
||||||
@ -174,18 +175,19 @@ class FakeCertificate(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class FakePolicy(BaseModel):
|
class FakePolicy(BaseModel):
|
||||||
def __init__(self, name, document, region_name):
|
def __init__(self, name, document, region_name, default_version_id="1"):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.document = document
|
self.document = document
|
||||||
self.arn = "arn:aws:iot:%s:1:policy/%s" % (region_name, name)
|
self.arn = "arn:aws:iot:%s:1:policy/%s" % (region_name, name)
|
||||||
self.version = "1" # TODO: handle version
|
self.default_version_id = default_version_id
|
||||||
|
self.versions = [FakePolicyVersion(self.name, document, True, region_name)]
|
||||||
|
|
||||||
def to_get_dict(self):
|
def to_get_dict(self):
|
||||||
return {
|
return {
|
||||||
"policyName": self.name,
|
"policyName": self.name,
|
||||||
"policyArn": self.arn,
|
"policyArn": self.arn,
|
||||||
"policyDocument": self.document,
|
"policyDocument": self.document,
|
||||||
"defaultVersionId": self.version,
|
"defaultVersionId": self.default_version_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
def to_dict_at_creation(self):
|
def to_dict_at_creation(self):
|
||||||
@ -193,13 +195,52 @@ class FakePolicy(BaseModel):
|
|||||||
"policyName": self.name,
|
"policyName": self.name,
|
||||||
"policyArn": self.arn,
|
"policyArn": self.arn,
|
||||||
"policyDocument": self.document,
|
"policyDocument": self.document,
|
||||||
"policyVersionId": self.version,
|
"policyVersionId": self.default_version_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
return {"policyName": self.name, "policyArn": self.arn}
|
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):
|
class FakeJob(BaseModel):
|
||||||
JOB_ID_REGEX_PATTERN = "[a-zA-Z0-9_-]"
|
JOB_ID_REGEX_PATTERN = "[a-zA-Z0-9_-]"
|
||||||
JOB_ID_REGEX = re.compile(JOB_ID_REGEX_PATTERN)
|
JOB_ID_REGEX = re.compile(JOB_ID_REGEX_PATTERN)
|
||||||
@ -226,12 +267,14 @@ class FakeJob(BaseModel):
|
|||||||
self.targets = targets
|
self.targets = targets
|
||||||
self.document_source = document_source
|
self.document_source = document_source
|
||||||
self.document = document
|
self.document = document
|
||||||
|
self.force = False
|
||||||
self.description = description
|
self.description = description
|
||||||
self.presigned_url_config = presigned_url_config
|
self.presigned_url_config = presigned_url_config
|
||||||
self.target_selection = target_selection
|
self.target_selection = target_selection
|
||||||
self.job_executions_rollout_config = job_executions_rollout_config
|
self.job_executions_rollout_config = job_executions_rollout_config
|
||||||
self.status = None # IN_PROGRESS | CANCELED | COMPLETED
|
self.status = "QUEUED" # IN_PROGRESS | CANCELED | COMPLETED
|
||||||
self.comment = None
|
self.comment = None
|
||||||
|
self.reason_code = None
|
||||||
self.created_at = time.mktime(datetime(2015, 1, 1).timetuple())
|
self.created_at = time.mktime(datetime(2015, 1, 1).timetuple())
|
||||||
self.last_updated_at = time.mktime(datetime(2015, 1, 1).timetuple())
|
self.last_updated_at = time.mktime(datetime(2015, 1, 1).timetuple())
|
||||||
self.completed_at = None
|
self.completed_at = None
|
||||||
@ -258,9 +301,11 @@ class FakeJob(BaseModel):
|
|||||||
"jobExecutionsRolloutConfig": self.job_executions_rollout_config,
|
"jobExecutionsRolloutConfig": self.job_executions_rollout_config,
|
||||||
"status": self.status,
|
"status": self.status,
|
||||||
"comment": self.comment,
|
"comment": self.comment,
|
||||||
|
"forceCanceled": self.force,
|
||||||
|
"reasonCode": self.reason_code,
|
||||||
"createdAt": self.created_at,
|
"createdAt": self.created_at,
|
||||||
"lastUpdatedAt": self.last_updated_at,
|
"lastUpdatedAt": self.last_updated_at,
|
||||||
"completedAt": self.completedAt,
|
"completedAt": self.completed_at,
|
||||||
"jobProcessDetails": self.job_process_details,
|
"jobProcessDetails": self.job_process_details,
|
||||||
"documentParameters": self.document_parameters,
|
"documentParameters": self.document_parameters,
|
||||||
"document": self.document,
|
"document": self.document,
|
||||||
@ -275,12 +320,67 @@ class FakeJob(BaseModel):
|
|||||||
return regex_match and length_match
|
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):
|
class IoTBackend(BaseBackend):
|
||||||
def __init__(self, region_name=None):
|
def __init__(self, region_name=None):
|
||||||
super(IoTBackend, self).__init__()
|
super(IoTBackend, self).__init__()
|
||||||
self.region_name = region_name
|
self.region_name = region_name
|
||||||
self.things = OrderedDict()
|
self.things = OrderedDict()
|
||||||
self.jobs = OrderedDict()
|
self.jobs = OrderedDict()
|
||||||
|
self.job_executions = OrderedDict()
|
||||||
self.thing_types = OrderedDict()
|
self.thing_types = OrderedDict()
|
||||||
self.thing_groups = OrderedDict()
|
self.thing_groups = OrderedDict()
|
||||||
self.certificates = OrderedDict()
|
self.certificates = OrderedDict()
|
||||||
@ -535,6 +635,28 @@ class IoTBackend(BaseBackend):
|
|||||||
self.policies[policy.name] = policy
|
self.policies[policy.name] = policy
|
||||||
return 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):
|
def list_policies(self):
|
||||||
policies = self.policies.values()
|
policies = self.policies.values()
|
||||||
return policies
|
return policies
|
||||||
@ -559,6 +681,60 @@ class IoTBackend(BaseBackend):
|
|||||||
policy = self.get_policy(policy_name)
|
policy = self.get_policy(policy_name)
|
||||||
del self.policies[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):
|
def _get_principal(self, principal_arn):
|
||||||
"""
|
"""
|
||||||
raise ResourceNotFoundException
|
raise ResourceNotFoundException
|
||||||
@ -574,14 +750,6 @@ class IoTBackend(BaseBackend):
|
|||||||
pass
|
pass
|
||||||
raise ResourceNotFoundException()
|
raise ResourceNotFoundException()
|
||||||
|
|
||||||
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 attach_principal_policy(self, policy_name, principal_arn):
|
def attach_principal_policy(self, policy_name, principal_arn):
|
||||||
principal = self._get_principal(principal_arn)
|
principal = self._get_principal(principal_arn)
|
||||||
policy = self.get_policy(policy_name)
|
policy = self.get_policy(policy_name)
|
||||||
@ -590,15 +758,6 @@ class IoTBackend(BaseBackend):
|
|||||||
return
|
return
|
||||||
self.principal_policies[k] = (principal, policy)
|
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 detach_principal_policy(self, policy_name, principal_arn):
|
def detach_principal_policy(self, policy_name, principal_arn):
|
||||||
# this may raises ResourceNotFoundException
|
# this may raises ResourceNotFoundException
|
||||||
self._get_principal(principal_arn)
|
self._get_principal(principal_arn)
|
||||||
@ -819,11 +978,187 @@ class IoTBackend(BaseBackend):
|
|||||||
self.region_name,
|
self.region_name,
|
||||||
)
|
)
|
||||||
self.jobs[job_id] = job
|
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
|
return job.job_arn, job_id, description
|
||||||
|
|
||||||
def describe_job(self, job_id):
|
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]
|
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
|
||||||
|
|
||||||
|
|
||||||
iot_backends = {}
|
iot_backends = {}
|
||||||
for region in Session().get_available_regions("iot"):
|
for region in Session().get_available_regions("iot"):
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
from six.moves.urllib.parse import unquote
|
||||||
|
|
||||||
from moto.core.responses import BaseResponse
|
from moto.core.responses import BaseResponse
|
||||||
from .models import iot_backends
|
from .models import iot_backends
|
||||||
@ -141,6 +142,8 @@ class IoTResponse(BaseResponse):
|
|||||||
createdAt=job.created_at,
|
createdAt=job.created_at,
|
||||||
description=job.description,
|
description=job.description,
|
||||||
documentParameters=job.document_parameters,
|
documentParameters=job.document_parameters,
|
||||||
|
forceCanceled=job.force,
|
||||||
|
reasonCode=job.reason_code,
|
||||||
jobArn=job.job_arn,
|
jobArn=job.job_arn,
|
||||||
jobExecutionsRolloutConfig=job.job_executions_rollout_config,
|
jobExecutionsRolloutConfig=job.job_executions_rollout_config,
|
||||||
jobId=job.job_id,
|
jobId=job.job_id,
|
||||||
@ -154,6 +157,127 @@ class IoTResponse(BaseResponse):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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):
|
def create_keys_and_certificate(self):
|
||||||
set_as_active = self._get_bool_param("setAsActive")
|
set_as_active = self._get_bool_param("setAsActive")
|
||||||
cert, key_pair = self.iot_backend.create_keys_and_certificate(
|
cert, key_pair = self.iot_backend.create_keys_and_certificate(
|
||||||
@ -241,12 +365,61 @@ class IoTResponse(BaseResponse):
|
|||||||
self.iot_backend.delete_policy(policy_name=policy_name)
|
self.iot_backend.delete_policy(policy_name=policy_name)
|
||||||
return json.dumps(dict())
|
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):
|
def attach_policy(self):
|
||||||
policy_name = self._get_param("policyName")
|
policy_name = self._get_param("policyName")
|
||||||
target = self._get_param("target")
|
target = self._get_param("target")
|
||||||
self.iot_backend.attach_policy(policy_name=policy_name, target=target)
|
self.iot_backend.attach_policy(policy_name=policy_name, target=target)
|
||||||
return json.dumps(dict())
|
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):
|
def attach_principal_policy(self):
|
||||||
policy_name = self._get_param("policyName")
|
policy_name = self._get_param("policyName")
|
||||||
principal = self.headers.get("x-amzn-iot-principal")
|
principal = self.headers.get("x-amzn-iot-principal")
|
||||||
|
@ -9,6 +9,173 @@ from botocore.exceptions import ClientError
|
|||||||
from nose.tools import assert_raises
|
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
|
@mock_iot
|
||||||
def test_things():
|
def test_things():
|
||||||
client = boto3.client("iot", region_name="ap-northeast-1")
|
client = boto3.client("iot", region_name="ap-northeast-1")
|
||||||
@ -994,7 +1161,10 @@ def test_create_job():
|
|||||||
client = boto3.client("iot", region_name="eu-west-1")
|
client = boto3.client("iot", region_name="eu-west-1")
|
||||||
name = "my-thing"
|
name = "my-thing"
|
||||||
job_id = "TestJob"
|
job_id = "TestJob"
|
||||||
# thing
|
# thing# job document
|
||||||
|
# job_document = {
|
||||||
|
# "field": "value"
|
||||||
|
# }
|
||||||
thing = client.create_thing(thingName=name)
|
thing = client.create_thing(thingName=name)
|
||||||
thing.should.have.key("thingName").which.should.equal(name)
|
thing.should.have.key("thingName").which.should.equal(name)
|
||||||
thing.should.have.key("thingArn")
|
thing.should.have.key("thingArn")
|
||||||
@ -1020,6 +1190,63 @@ def test_create_job():
|
|||||||
job.should.have.key("description")
|
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
|
@mock_iot
|
||||||
def test_describe_job():
|
def test_describe_job():
|
||||||
client = boto3.client("iot", region_name="eu-west-1")
|
client = boto3.client("iot", region_name="eu-west-1")
|
||||||
@ -1124,3 +1351,387 @@ def test_describe_job_1():
|
|||||||
job.should.have.key("job").which.should.have.key(
|
job.should.have.key("job").which.should.have.key(
|
||||||
"jobExecutionsRolloutConfig"
|
"jobExecutionsRolloutConfig"
|
||||||
).which.should.have.key("maximumPerMinute").which.should.equal(10)
|
).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
|
||||||
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user