SSM - Maintenance Windows (#4549)
This commit is contained in:
parent
c62a34528e
commit
db0738025a
@ -4196,7 +4196,7 @@
|
||||
|
||||
## ssm
|
||||
<details>
|
||||
<summary>17% implemented</summary>
|
||||
<summary>20% implemented</summary>
|
||||
|
||||
- [X] add_tags_to_resource
|
||||
- [ ] associate_ops_item_related_item
|
||||
@ -4206,7 +4206,7 @@
|
||||
- [ ] create_association
|
||||
- [ ] create_association_batch
|
||||
- [X] create_document
|
||||
- [ ] create_maintenance_window
|
||||
- [X] create_maintenance_window
|
||||
- [ ] create_ops_item
|
||||
- [ ] create_ops_metadata
|
||||
- [ ] create_patch_baseline
|
||||
@ -4215,7 +4215,7 @@
|
||||
- [ ] delete_association
|
||||
- [X] delete_document
|
||||
- [ ] delete_inventory
|
||||
- [ ] delete_maintenance_window
|
||||
- [X] delete_maintenance_window
|
||||
- [ ] delete_ops_metadata
|
||||
- [X] delete_parameter
|
||||
- [X] delete_parameters
|
||||
@ -4248,7 +4248,7 @@
|
||||
- [ ] describe_maintenance_window_schedule
|
||||
- [ ] describe_maintenance_window_targets
|
||||
- [ ] describe_maintenance_window_tasks
|
||||
- [ ] describe_maintenance_windows
|
||||
- [X] describe_maintenance_windows
|
||||
- [ ] describe_maintenance_windows_for_target
|
||||
- [ ] describe_ops_items
|
||||
- [X] describe_parameters
|
||||
@ -4267,7 +4267,7 @@
|
||||
- [X] get_document
|
||||
- [ ] get_inventory
|
||||
- [ ] get_inventory_schema
|
||||
- [ ] get_maintenance_window
|
||||
- [X] get_maintenance_window
|
||||
- [ ] get_maintenance_window_execution
|
||||
- [ ] get_maintenance_window_execution_task
|
||||
- [ ] get_maintenance_window_execution_task_invocation
|
||||
|
@ -35,7 +35,11 @@ ssm
|
||||
- [ ] create_association
|
||||
- [ ] create_association_batch
|
||||
- [X] create_document
|
||||
- [ ] create_maintenance_window
|
||||
- [X] create_maintenance_window
|
||||
|
||||
Creates a maintenance window. No error handling or input validation has been implemented yet.
|
||||
|
||||
|
||||
- [ ] create_ops_item
|
||||
- [ ] create_ops_metadata
|
||||
- [ ] create_patch_baseline
|
||||
@ -44,7 +48,11 @@ ssm
|
||||
- [ ] delete_association
|
||||
- [X] delete_document
|
||||
- [ ] delete_inventory
|
||||
- [ ] delete_maintenance_window
|
||||
- [X] delete_maintenance_window
|
||||
|
||||
Assumes the provided WindowId exists. No error handling has been implemented yet.
|
||||
|
||||
|
||||
- [ ] delete_ops_metadata
|
||||
- [X] delete_parameter
|
||||
- [X] delete_parameters
|
||||
@ -77,7 +85,13 @@ ssm
|
||||
- [ ] describe_maintenance_window_schedule
|
||||
- [ ] describe_maintenance_window_targets
|
||||
- [ ] describe_maintenance_window_tasks
|
||||
- [ ] describe_maintenance_windows
|
||||
- [X] describe_maintenance_windows
|
||||
|
||||
Returns all windows. No pagination has been implemented yet. Only filtering for Name is supported.
|
||||
The NextExecutionTime-field is not returned.
|
||||
|
||||
|
||||
|
||||
- [ ] describe_maintenance_windows_for_target
|
||||
- [ ] describe_ops_items
|
||||
- [X] describe_parameters
|
||||
@ -100,7 +114,12 @@ ssm
|
||||
- [X] get_document
|
||||
- [ ] get_inventory
|
||||
- [ ] get_inventory_schema
|
||||
- [ ] get_maintenance_window
|
||||
- [X] get_maintenance_window
|
||||
|
||||
The window is assumed to exist - no error handling has been implemented yet.
|
||||
The NextExecutionTime-field is not returned.
|
||||
|
||||
|
||||
- [ ] get_maintenance_window_execution
|
||||
- [ ] get_maintenance_window_execution_task
|
||||
- [ ] get_maintenance_window_execution_task_invocation
|
||||
|
@ -15,6 +15,7 @@ import uuid
|
||||
import json
|
||||
import yaml
|
||||
import hashlib
|
||||
import random
|
||||
|
||||
from .utils import parameter_arn
|
||||
from .exceptions import (
|
||||
@ -669,6 +670,53 @@ def _valid_parameter_data_type(data_type):
|
||||
return data_type in ("text", "aws:ec2:image")
|
||||
|
||||
|
||||
class FakeMaintenanceWindow:
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
description,
|
||||
enabled,
|
||||
duration,
|
||||
cutoff,
|
||||
schedule,
|
||||
schedule_timezone,
|
||||
schedule_offset,
|
||||
start_date,
|
||||
end_date,
|
||||
):
|
||||
self.id = FakeMaintenanceWindow.generate_id()
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.enabled = enabled
|
||||
self.duration = duration
|
||||
self.cutoff = cutoff
|
||||
self.schedule = schedule
|
||||
self.schedule_timezone = schedule_timezone
|
||||
self.schedule_offset = schedule_offset
|
||||
self.start_date = start_date
|
||||
self.end_date = end_date
|
||||
|
||||
def to_json(self):
|
||||
return {
|
||||
"WindowId": self.id,
|
||||
"Name": self.name,
|
||||
"Description": self.description,
|
||||
"Enabled": self.enabled,
|
||||
"Duration": self.duration,
|
||||
"Cutoff": self.cutoff,
|
||||
"Schedule": self.schedule,
|
||||
"ScheduleTimezone": self.schedule_timezone,
|
||||
"ScheduleOffset": self.schedule_offset,
|
||||
"StartDate": self.start_date,
|
||||
"EndDate": self.end_date,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def generate_id():
|
||||
chars = list(range(10)) + ["a", "b", "c", "d", "e", "f"]
|
||||
return "mw-" + "".join(str(random.choice(chars)) for _ in range(17))
|
||||
|
||||
|
||||
class SimpleSystemManagerBackend(BaseBackend):
|
||||
def __init__(self, region_name=None):
|
||||
super(SimpleSystemManagerBackend, self).__init__()
|
||||
@ -681,6 +729,8 @@ class SimpleSystemManagerBackend(BaseBackend):
|
||||
self._errors = []
|
||||
self._documents: Dict[str, Documents] = {}
|
||||
|
||||
self.windows: Dict[str, FakeMaintenanceWindow] = dict()
|
||||
|
||||
self._region = region_name
|
||||
|
||||
def reset(self):
|
||||
@ -1735,6 +1785,63 @@ class SimpleSystemManagerBackend(BaseBackend):
|
||||
command = self.get_command_by_id(command_id)
|
||||
return command.get_invocation(instance_id, plugin_name)
|
||||
|
||||
def create_maintenance_window(
|
||||
self,
|
||||
name,
|
||||
description,
|
||||
enabled,
|
||||
duration,
|
||||
cutoff,
|
||||
schedule,
|
||||
schedule_timezone,
|
||||
schedule_offset,
|
||||
start_date,
|
||||
end_date,
|
||||
):
|
||||
"""
|
||||
Creates a maintenance window. No error handling or input validation has been implemented yet.
|
||||
"""
|
||||
window = FakeMaintenanceWindow(
|
||||
name,
|
||||
description,
|
||||
enabled,
|
||||
duration,
|
||||
cutoff,
|
||||
schedule,
|
||||
schedule_timezone,
|
||||
schedule_offset,
|
||||
start_date,
|
||||
end_date,
|
||||
)
|
||||
self.windows[window.id] = window
|
||||
return window.id
|
||||
|
||||
def get_maintenance_window(self, window_id):
|
||||
"""
|
||||
The window is assumed to exist - no error handling has been implemented yet.
|
||||
The NextExecutionTime-field is not returned.
|
||||
"""
|
||||
return self.windows[window_id]
|
||||
|
||||
def describe_maintenance_windows(self, filters):
|
||||
"""
|
||||
Returns all windows. No pagination has been implemented yet. Only filtering for Name is supported.
|
||||
The NextExecutionTime-field is not returned.
|
||||
|
||||
"""
|
||||
res = [window for window in self.windows.values()]
|
||||
if filters:
|
||||
for f in filters:
|
||||
if f["Key"] == "Name":
|
||||
res = [w for w in res if w.name in f["Values"]]
|
||||
return res
|
||||
|
||||
def delete_maintenance_window(self, window_id):
|
||||
"""
|
||||
Assumes the provided WindowId exists. No error handling has been implemented yet.
|
||||
"""
|
||||
del self.windows[window_id]
|
||||
|
||||
|
||||
ssm_backends = {}
|
||||
for region in Session().get_available_regions("ssm"):
|
||||
|
@ -379,3 +379,46 @@ class SimpleSystemManagerResponse(BaseResponse):
|
||||
return json.dumps(
|
||||
self.ssm_backend.get_command_invocation(**self.request_params)
|
||||
)
|
||||
|
||||
def create_maintenance_window(self):
|
||||
name = self._get_param("Name")
|
||||
desc = self._get_param("Description", None)
|
||||
enabled = self._get_bool_param("Enabled", True)
|
||||
duration = self._get_int_param("Duration")
|
||||
cutoff = self._get_int_param("Cutoff")
|
||||
schedule = self._get_param("Schedule")
|
||||
schedule_timezone = self._get_param("ScheduleTimezone")
|
||||
schedule_offset = self._get_int_param("ScheduleOffset")
|
||||
start_date = self._get_param("StartDate")
|
||||
end_date = self._get_param("EndDate")
|
||||
window_id = self.ssm_backend.create_maintenance_window(
|
||||
name=name,
|
||||
description=desc,
|
||||
enabled=enabled,
|
||||
duration=duration,
|
||||
cutoff=cutoff,
|
||||
schedule=schedule,
|
||||
schedule_timezone=schedule_timezone,
|
||||
schedule_offset=schedule_offset,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
)
|
||||
return json.dumps({"WindowId": window_id})
|
||||
|
||||
def get_maintenance_window(self):
|
||||
window_id = self._get_param("WindowId")
|
||||
window = self.ssm_backend.get_maintenance_window(window_id)
|
||||
return json.dumps(window.to_json())
|
||||
|
||||
def describe_maintenance_windows(self):
|
||||
filters = self._get_param("Filters", None)
|
||||
windows = [
|
||||
window.to_json()
|
||||
for window in self.ssm_backend.describe_maintenance_windows(filters)
|
||||
]
|
||||
return json.dumps({"WindowIdentities": windows})
|
||||
|
||||
def delete_maintenance_window(self):
|
||||
window_id = self._get_param("WindowId")
|
||||
self.ssm_backend.delete_maintenance_window(window_id)
|
||||
return "{}"
|
||||
|
155
tests/test_ssm/test_ssm_maintenance_windows.py
Normal file
155
tests/test_ssm/test_ssm_maintenance_windows.py
Normal file
@ -0,0 +1,155 @@
|
||||
import boto3
|
||||
import sure # noqa # pylint: disable=unused-import
|
||||
|
||||
from moto import mock_ssm
|
||||
|
||||
|
||||
@mock_ssm
|
||||
def test_describe_maintenance_window():
|
||||
ssm = boto3.client("ssm", region_name="us-east-1")
|
||||
|
||||
resp = ssm.describe_maintenance_windows()
|
||||
resp.should.have.key("WindowIdentities").equals([])
|
||||
|
||||
resp = ssm.describe_maintenance_windows(
|
||||
Filters=[{"Key": "Name", "Values": ["fake-maintenance-window-name",]},],
|
||||
)
|
||||
resp.should.have.key("WindowIdentities").equals([])
|
||||
|
||||
|
||||
@mock_ssm
|
||||
def test_create_maintenance_windows_simple():
|
||||
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,
|
||||
)
|
||||
resp.should.have.key("WindowId")
|
||||
_id = resp["WindowId"] # mw-01d6bbfdf6af2c39a
|
||||
|
||||
resp = ssm.describe_maintenance_windows()
|
||||
resp.should.have.key("WindowIdentities").have.length_of(1)
|
||||
|
||||
my_window = resp["WindowIdentities"][0]
|
||||
my_window.should.have.key("WindowId").equal(_id)
|
||||
my_window.should.have.key("Name").equal("simple-window")
|
||||
my_window.should.have.key("Enabled").equal(True)
|
||||
my_window.should.have.key("Duration").equal(2)
|
||||
my_window.should.have.key("Cutoff").equal(1)
|
||||
my_window.should.have.key("Schedule").equal("cron(15 12 * * ? *)")
|
||||
# my_window.should.have.key("NextExecutionTime")
|
||||
my_window.shouldnt.have.key("Description")
|
||||
my_window.shouldnt.have.key("ScheduleTimezone")
|
||||
my_window.shouldnt.have.key("ScheduleOffset")
|
||||
my_window.shouldnt.have.key("EndDate")
|
||||
my_window.shouldnt.have.key("StartDate")
|
||||
|
||||
|
||||
@mock_ssm
|
||||
def test_create_maintenance_windows_advanced():
|
||||
ssm = boto3.client("ssm", region_name="us-east-1")
|
||||
|
||||
resp = ssm.create_maintenance_window(
|
||||
Name="simple-window",
|
||||
Description="French windows are just too fancy",
|
||||
Schedule="cron(15 12 * * ? *)",
|
||||
ScheduleTimezone="Europe/London",
|
||||
ScheduleOffset=1,
|
||||
Duration=5,
|
||||
Cutoff=4,
|
||||
AllowUnassociatedTargets=False,
|
||||
StartDate="2021-11-01",
|
||||
EndDate="2021-12-31",
|
||||
)
|
||||
resp.should.have.key("WindowId")
|
||||
_id = resp["WindowId"] # mw-01d6bbfdf6af2c39a
|
||||
|
||||
resp = ssm.describe_maintenance_windows()
|
||||
resp.should.have.key("WindowIdentities").have.length_of(1)
|
||||
|
||||
my_window = resp["WindowIdentities"][0]
|
||||
my_window.should.have.key("WindowId").equal(_id)
|
||||
my_window.should.have.key("Name").equal("simple-window")
|
||||
my_window.should.have.key("Enabled").equal(True)
|
||||
my_window.should.have.key("Duration").equal(5)
|
||||
my_window.should.have.key("Cutoff").equal(4)
|
||||
my_window.should.have.key("Schedule").equal("cron(15 12 * * ? *)")
|
||||
# my_window.should.have.key("NextExecutionTime")
|
||||
my_window.should.have.key("Description").equals("French windows are just too fancy")
|
||||
my_window.should.have.key("ScheduleTimezone").equals("Europe/London")
|
||||
my_window.should.have.key("ScheduleOffset").equals(1)
|
||||
my_window.should.have.key("StartDate").equals("2021-11-01")
|
||||
my_window.should.have.key("EndDate").equals("2021-12-31")
|
||||
|
||||
|
||||
@mock_ssm
|
||||
def test_get_maintenance_windows():
|
||||
ssm = boto3.client("ssm", region_name="us-east-1")
|
||||
|
||||
resp = ssm.create_maintenance_window(
|
||||
Name="my-window",
|
||||
Schedule="cron(15 12 * * ? *)",
|
||||
Duration=2,
|
||||
Cutoff=1,
|
||||
AllowUnassociatedTargets=False,
|
||||
)
|
||||
resp.should.have.key("WindowId")
|
||||
_id = resp["WindowId"] # mw-01d6bbfdf6af2c39a
|
||||
|
||||
my_window = ssm.get_maintenance_window(WindowId=_id)
|
||||
my_window.should.have.key("WindowId").equal(_id)
|
||||
my_window.should.have.key("Name").equal("my-window")
|
||||
my_window.should.have.key("Enabled").equal(True)
|
||||
my_window.should.have.key("Duration").equal(2)
|
||||
my_window.should.have.key("Cutoff").equal(1)
|
||||
my_window.should.have.key("Schedule").equal("cron(15 12 * * ? *)")
|
||||
# my_window.should.have.key("NextExecutionTime")
|
||||
my_window.shouldnt.have.key("Description")
|
||||
my_window.shouldnt.have.key("ScheduleTimezone")
|
||||
my_window.shouldnt.have.key("ScheduleOffset")
|
||||
my_window.shouldnt.have.key("EndDate")
|
||||
my_window.shouldnt.have.key("StartDate")
|
||||
|
||||
|
||||
@mock_ssm
|
||||
def test_describe_maintenance_windows():
|
||||
ssm = boto3.client("ssm", region_name="us-east-1")
|
||||
|
||||
for idx in range(0, 4):
|
||||
ssm.create_maintenance_window(
|
||||
Name=f"window_{idx}",
|
||||
Schedule="cron(15 12 * * ? *)",
|
||||
Duration=2,
|
||||
Cutoff=1,
|
||||
AllowUnassociatedTargets=False,
|
||||
)
|
||||
|
||||
resp = ssm.describe_maintenance_windows()
|
||||
resp.should.have.key("WindowIdentities").have.length_of(4)
|
||||
|
||||
resp = ssm.describe_maintenance_windows(
|
||||
Filters=[{"Key": "Name", "Values": ["window_0", "window_2",]},],
|
||||
)
|
||||
resp.should.have.key("WindowIdentities").have.length_of(2)
|
||||
|
||||
|
||||
@mock_ssm
|
||||
def test_delete_maintenance_windows():
|
||||
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,
|
||||
)
|
||||
|
||||
ssm.delete_maintenance_window(WindowId=(resp["WindowId"]))
|
||||
|
||||
resp = ssm.describe_maintenance_windows()
|
||||
resp.should.have.key("WindowIdentities").equals([])
|
Loading…
Reference in New Issue
Block a user