SSM: add support for maintenance window tasks (#6430)

This commit is contained in:
Cristopher Pinzón 2023-06-28 16:30:11 -05:00 committed by GitHub
parent 22774e8d08
commit 8ba1a61424
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 275 additions and 3 deletions

View File

@ -6784,7 +6784,7 @@
- [ ] deregister_managed_instance - [ ] deregister_managed_instance
- [ ] deregister_patch_baseline_for_patch_group - [ ] deregister_patch_baseline_for_patch_group
- [X] deregister_target_from_maintenance_window - [X] deregister_target_from_maintenance_window
- [ ] deregister_task_from_maintenance_window - [X] deregister_task_from_maintenance_window
- [ ] describe_activations - [ ] describe_activations
- [ ] describe_association - [ ] describe_association
- [ ] describe_association_execution_targets - [ ] describe_association_execution_targets
@ -6807,7 +6807,7 @@
- [ ] describe_maintenance_window_executions - [ ] describe_maintenance_window_executions
- [ ] describe_maintenance_window_schedule - [ ] describe_maintenance_window_schedule
- [X] describe_maintenance_window_targets - [X] describe_maintenance_window_targets
- [ ] describe_maintenance_window_tasks - [X] describe_maintenance_window_tasks
- [X] describe_maintenance_windows - [X] describe_maintenance_windows
- [ ] describe_maintenance_windows_for_target - [ ] describe_maintenance_windows_for_target
- [ ] describe_ops_items - [ ] describe_ops_items
@ -6868,7 +6868,7 @@
- [ ] register_default_patch_baseline - [ ] register_default_patch_baseline
- [ ] register_patch_baseline_for_patch_group - [ ] register_patch_baseline_for_patch_group
- [X] register_target_with_maintenance_window - [X] register_target_with_maintenance_window
- [ ] register_task_with_maintenance_window - [X] register_task_with_maintenance_window
- [X] remove_tags_from_resource - [X] remove_tags_from_resource
- [ ] reset_service_setting - [ ] reset_service_setting
- [ ] resume_session - [ ] resume_session

View File

@ -975,6 +975,65 @@ class FakeMaintenanceWindowTarget:
return str(random.uuid4()) return str(random.uuid4())
class FakeMaintenanceWindowTask:
def __init__(
self,
window_id: str,
targets: Optional[List[Dict[str, Any]]],
task_arn: str,
service_role_arn: Optional[str],
task_type: str,
task_parameters: Optional[Dict[str, Any]],
task_invocation_parameters: Optional[Dict[str, Any]],
priority: Optional[int],
max_concurrency: Optional[str],
max_errors: Optional[str],
logging_info: Optional[Dict[str, Any]],
name: Optional[str],
description: Optional[str],
cutoff_behavior: Optional[str],
alarm_configurations: Optional[Dict[str, Any]],
):
self.window_task_id = FakeMaintenanceWindowTask.generate_id()
self.window_id = window_id
self.task_type = task_type
self.task_arn = task_arn
self.service_role_arn = service_role_arn
self.task_parameters = task_parameters
self.priority = priority
self.max_concurrency = max_concurrency
self.max_errors = max_errors
self.logging_info = logging_info
self.name = name
self.description = description
self.targets = targets
self.task_invocation_parameters = task_invocation_parameters
self.cutoff_behavior = cutoff_behavior
self.alarm_configurations = alarm_configurations
def to_json(self) -> Dict[str, Any]:
return {
"WindowId": self.window_id,
"WindowTaskId": self.window_task_id,
"TaskType": self.task_type,
"TaskArn": self.task_arn,
"ServiceRoleArn": self.service_role_arn,
"TaskParameters": self.task_parameters,
"Priority": self.priority,
"MaxConcurrency": self.max_concurrency,
"MaxErrors": self.max_errors,
"LoggingInfo": self.logging_info,
"Name": self.name,
"Description": self.description,
"Targets": self.targets,
"TaskInvocationParameters": self.task_invocation_parameters,
}
@staticmethod
def generate_id() -> str:
return str(random.uuid4())
def _maintenance_window_target_filter_match( def _maintenance_window_target_filter_match(
filters: Optional[List[Dict[str, Any]]], target: FakeMaintenanceWindowTarget filters: Optional[List[Dict[str, Any]]], target: FakeMaintenanceWindowTarget
) -> bool: ) -> bool:
@ -983,6 +1042,14 @@ def _maintenance_window_target_filter_match(
return False return False
def _maintenance_window_task_filter_match(
filters: Optional[List[Dict[str, Any]]], task: FakeMaintenanceWindowTask
) -> bool:
if not filters and task:
return True
return False
class FakeMaintenanceWindow: class FakeMaintenanceWindow:
def __init__( def __init__(
self, self,
@ -1007,6 +1074,7 @@ class FakeMaintenanceWindow:
self.start_date = start_date self.start_date = start_date
self.end_date = end_date self.end_date = end_date
self.targets: Dict[str, FakeMaintenanceWindowTarget] = {} self.targets: Dict[str, FakeMaintenanceWindowTarget] = {}
self.tasks: Dict[str, FakeMaintenanceWindowTask] = {}
def to_json(self) -> Dict[str, Any]: def to_json(self) -> Dict[str, Any]:
return { return {
@ -2351,5 +2419,62 @@ class SimpleSystemManagerBackend(BaseBackend):
] ]
return targets return targets
def register_task_with_maintenance_window(
self,
window_id: str,
targets: Optional[List[Dict[str, Any]]],
task_arn: str,
service_role_arn: Optional[str],
task_type: str,
task_parameters: Optional[Dict[str, Any]],
task_invocation_parameters: Optional[Dict[str, Any]],
priority: Optional[int],
max_concurrency: Optional[str],
max_errors: Optional[str],
logging_info: Optional[Dict[str, Any]],
name: Optional[str],
description: Optional[str],
cutoff_behavior: Optional[str],
alarm_configurations: Optional[Dict[str, Any]],
) -> str:
window = self.get_maintenance_window(window_id)
task = FakeMaintenanceWindowTask(
window_id,
targets,
task_arn,
service_role_arn,
task_type,
task_parameters,
task_invocation_parameters,
priority,
max_concurrency,
max_errors,
logging_info,
name,
description,
cutoff_behavior,
alarm_configurations,
)
window.tasks[task.window_task_id] = task
return task.window_task_id
def describe_maintenance_window_tasks(
self, window_id: str, filters: List[Dict[str, Any]]
) -> List[FakeMaintenanceWindowTask]:
window = self.get_maintenance_window(window_id)
tasks = [
task
for task in window.tasks.values()
if _maintenance_window_task_filter_match(filters, task)
]
return tasks
def deregister_task_from_maintenance_window(
self, window_id: str, window_task_id: str
) -> None:
window = self.get_maintenance_window(window_id)
del window.tasks[window_task_id]
ssm_backends = BackendDict(SimpleSystemManagerBackend, "ssm") ssm_backends = BackendDict(SimpleSystemManagerBackend, "ssm")

View File

@ -513,3 +513,42 @@ class SimpleSystemManagerResponse(BaseResponse):
baseline_id = self._get_param("BaselineId") baseline_id = self._get_param("BaselineId")
self.ssm_backend.delete_patch_baseline(baseline_id) self.ssm_backend.delete_patch_baseline(baseline_id)
return "{}" return "{}"
def register_task_with_maintenance_window(self) -> str:
window_task_id = self.ssm_backend.register_task_with_maintenance_window(
window_id=self._get_param("WindowId"),
targets=self._get_param("Targets"),
task_arn=self._get_param("TaskArn"),
service_role_arn=self._get_param("ServiceRoleArn"),
task_type=self._get_param("TaskType"),
task_parameters=self._get_param("TaskParameters"),
task_invocation_parameters=self._get_param("TaskInvocationParameters"),
priority=self._get_param("Priority"),
max_concurrency=self._get_param("MaxConcurrency"),
max_errors=self._get_param("MaxErrors"),
logging_info=self._get_param("LoggingInfo"),
name=self._get_param("Name"),
description=self._get_param("Description"),
cutoff_behavior=self._get_param("CutoffBehavior"),
alarm_configurations=self._get_param("AlarmConfigurations"),
)
return json.dumps({"WindowTaskId": window_task_id})
def describe_maintenance_window_tasks(self) -> str:
window_id = self._get_param("WindowId")
filters = self._get_param("Filters", [])
tasks = [
task.to_json()
for task in self.ssm_backend.describe_maintenance_window_tasks(
window_id, filters
)
]
return json.dumps({"Tasks": tasks})
def deregister_task_from_maintenance_window(self) -> str:
window_id = self._get_param("WindowId")
window_task_id = self._get_param("WindowTaskId")
self.ssm_backend.deregister_task_from_maintenance_window(
window_id, window_task_id
)
return "{}"

View File

@ -274,3 +274,111 @@ def test_deregister_target_from_maintenance_window():
WindowId=window_id, WindowId=window_id,
) )
resp.should.have.key("Targets").should.have.length_of(0) resp.should.have.key("Targets").should.have.length_of(0)
@mock_ssm
def test_describe_maintenance_window_with_no_task_or_targets():
ssm = boto3.client("ssm", region_name="us-east-1")
resp = ssm.create_maintenance_window(
Name="simple-window",
Schedule="cron(15 12 * * ? *)",
Duration=2,
Cutoff=1,
AllowUnassociatedTargets=False,
)
window_id = resp["WindowId"]
resp = ssm.describe_maintenance_window_tasks(
WindowId=window_id,
)
resp.should.have.key("Tasks").should.have.length_of(0)
resp = ssm.describe_maintenance_window_targets(
WindowId=window_id,
)
resp.should.have.key("Targets").should.have.length_of(0)
@mock_ssm
def test_register_maintenance_window_task():
ssm = boto3.client("ssm", region_name="us-east-1")
resp = ssm.create_maintenance_window(
Name="simple-window",
Schedule="cron(15 12 * * ? *)",
Duration=2,
Cutoff=1,
AllowUnassociatedTargets=False,
)
window_id = resp["WindowId"]
resp = ssm.register_target_with_maintenance_window(
WindowId=window_id,
ResourceType="INSTANCE",
Targets=[{"Key": "tag:Name", "Values": ["my-instance"]}],
)
window_target_id = resp["WindowTargetId"]
resp = ssm.register_task_with_maintenance_window(
WindowId=window_id,
Targets=[{"Key": "WindowTargetIds", "Values": [window_target_id]}],
TaskArn="AWS-RunShellScript",
TaskType="RUN_COMMAND",
MaxConcurrency="1",
MaxErrors="1",
)
resp.should.have.key("WindowTaskId")
_id = resp["WindowTaskId"]
resp = ssm.describe_maintenance_window_tasks(
WindowId=window_id,
)
resp.should.have.key("Tasks").should.have.length_of(1)
resp["Tasks"][0].should.have.key("WindowTaskId").equal(_id)
resp["Tasks"][0].should.have.key("WindowId").equal(window_id)
resp["Tasks"][0].should.have.key("TaskArn").equal("AWS-RunShellScript")
resp["Tasks"][0].should.have.key("MaxConcurrency").equal("1")
resp["Tasks"][0].should.have.key("MaxErrors").equal("1")
@mock_ssm
def test_deregister_maintenance_window_task():
ssm = boto3.client("ssm", region_name="us-east-1")
resp = ssm.create_maintenance_window(
Name="simple-window",
Schedule="cron(15 12 * * ? *)",
Duration=2,
Cutoff=1,
AllowUnassociatedTargets=False,
)
window_id = resp["WindowId"]
resp = ssm.register_target_with_maintenance_window(
WindowId=window_id,
ResourceType="INSTANCE",
Targets=[{"Key": "tag:Name", "Values": ["my-instance"]}],
)
window_target_id = resp["WindowTargetId"]
resp = ssm.register_task_with_maintenance_window(
WindowId=window_id,
Targets=[{"Key": "WindowTargetIds", "Values": [window_target_id]}],
TaskArn="AWS-RunShellScript",
TaskType="RUN_COMMAND",
MaxConcurrency="1",
MaxErrors="1",
)
window_task_id = resp["WindowTaskId"]
ssm.deregister_task_from_maintenance_window(
WindowId=window_id,
WindowTaskId=window_task_id,
)
resp = ssm.describe_maintenance_window_tasks(
WindowId=window_id,
)
resp.should.have.key("Tasks").should.have.length_of(0)