diff --git a/.doc_gen/metadata/ssm_metadata.yaml b/.doc_gen/metadata/ssm_metadata.yaml index 8b8a9a7cb84..5897bc01855 100644 --- a/.doc_gen/metadata/ssm_metadata.yaml +++ b/.doc_gen/metadata/ssm_metadata.yaml @@ -13,6 +13,15 @@ ssm_Hello: - description: snippet_tags: - ssm.java2.hello.main + Python: + versions: + - sdk_version: 3 + github: python/example_code/ssm + sdkguide: + excerpts: + - description: + snippet_tags: + - python.example_code.ssm.Hello services: ssm: {listThings} ssm_DescribeParameters: @@ -68,6 +77,16 @@ ssm_CreateOpsItem: - description: snippet_tags: - ssm.java2.create_ops.main + Python: + versions: + - sdk_version: 3 + github: python/example_code/ssm + sdkguide: + excerpts: + - description: + snippet_tags: + - python.example_code.ssm.OpsItemWrapper.decl + - python.example_code.ssm.CreateOpsItem services: ssm: {CreateOpsItem} ssm_UpdateMaintenanceWindow: @@ -81,6 +100,16 @@ ssm_UpdateMaintenanceWindow: - description: snippet_tags: - ssm.java2.update_window.main + Python: + versions: + - sdk_version: 3 + github: python/example_code/ssm + sdkguide: + excerpts: + - description: + snippet_tags: + - python.example_code.ssm.MaintenanceWindowWrapper.decl + - python.example_code.ssm.UpdateMaintenanceWindow services: ssm: {UpdateMaintenanceWindow} ssm_CreateMaintenanceWindow: @@ -94,6 +123,16 @@ ssm_CreateMaintenanceWindow: - description: snippet_tags: - ssm.java2.create_window.main + Python: + versions: + - sdk_version: 3 + github: python/example_code/ssm + sdkguide: + excerpts: + - description: + snippet_tags: + - python.example_code.ssm.MaintenanceWindowWrapper.decl + - python.example_code.ssm.CreateMaintenanceWindow services: ssm: {CreateMaintenanceWindow} ssm_SendCommand: @@ -107,8 +146,32 @@ ssm_SendCommand: - description: snippet_tags: - ssm.Java2.send_command.main + Python: + versions: + - sdk_version: 3 + github: python/example_code/ssm + sdkguide: + excerpts: + - description: + snippet_tags: + - python.example_code.ssm.DocumentWrapper.decl + - python.example_code.ssm.SendCommand services: ssm: {SendCommand} +ssm_ListCommands: + languages: + Python: + versions: + - sdk_version: 3 + github: python/example_code/ssm + sdkguide: + excerpts: + - description: + snippet_tags: + - python.example_code.ssm.DocumentWrapper.decl + - python.example_code.ssm.ListCommands + services: + ssm: {ListCommands} ssm_CreateDocument: languages: Java: @@ -120,6 +183,16 @@ ssm_CreateDocument: - description: snippet_tags: - ssm.java2.create_doc.main + Python: + versions: + - sdk_version: 3 + github: python/example_code/ssm + sdkguide: + excerpts: + - description: + snippet_tags: + - python.example_code.ssm.DocumentWrapper.decl + - python.example_code.ssm.CreateDocument services: ssm: {CreateDocument} ssm_DescribeOpsItems: @@ -133,8 +206,32 @@ ssm_DescribeOpsItems: - description: snippet_tags: - ssm.java2.describe_ops.main + Python: + versions: + - sdk_version: 3 + github: python/example_code/ssm + sdkguide: + excerpts: + - description: + snippet_tags: + - python.example_code.ssm.OpsItemWrapper.decl + - python.example_code.ssm.DescribeOpsItem services: ssm: {DescribeOpsItems} +ssm_DeleteOpsItems: + languages: + Python: + versions: + - sdk_version: 3 + github: python/example_code/ssm + sdkguide: + excerpts: + - description: + snippet_tags: + - python.example_code.ssm.OpsItemWrapper.decl + - python.example_code.ssm.DeleteOpsItem + services: + ssm: {DeleteOpsItem} ssm_DeleteMaintenanceWindow: languages: Java: @@ -146,6 +243,16 @@ ssm_DeleteMaintenanceWindow: - description: snippet_tags: - ssm.java2.delete_window.main + Python: + versions: + - sdk_version: 3 + github: python/example_code/ssm + sdkguide: + excerpts: + - description: + snippet_tags: + - python.example_code.ssm.MaintenanceWindowWrapper.decl + - python.example_code.ssm.DeleteMaintenanceWindow services: ssm: {DeleteMaintenanceWindow} ssm_DeleteDocument: @@ -159,6 +266,16 @@ ssm_DeleteDocument: - description: snippet_tags: - ssm.Java2.delete_doc.main + Python: + versions: + - sdk_version: 3 + github: python/example_code/ssm + sdkguide: + excerpts: + - description: + snippet_tags: + - python.example_code.ssm.DocumentWrapper.decl + - python.example_code.ssm.DeleteDocument services: ssm: {DeleteDocument} ssm_UpdateOpsItem: @@ -172,6 +289,16 @@ ssm_UpdateOpsItem: - description: snippet_tags: - ssm.Java2.resolve_ops.main + Python: + versions: + - sdk_version: 3 + github: python/example_code/ssm + sdkguide: + excerpts: + - description: + snippet_tags: + - python.example_code.ssm.OpsItemWrapper.decl + - python.example_code.ssm.UpdateOpsItem services: ssm: {UpdateOpsItem} ssm_Scenario: @@ -189,6 +316,24 @@ ssm_Scenario: - description: snippet_tags: - ssm.java2.scenario.main + Python: + versions: + - sdk_version: 3 + github: python/example_code/ssm + sdkguide: + excerpts: + - description: Run an interactive scenario at a command prompt. + snippet_tags: + - python.example_code.ssm.Scenario_GetStartedSSM + - description: Define a class that wraps document and command actions. + snippet_tags: + - python.example_code.ssm.DocumentWrapper.class + - description: Define a class that wraps ops item actions. + snippet_tags: + - python.example_code.ssm.OpsItemWrapper.class + - description: Define a class that wraps maintenance window actions. + snippet_tags: + - python.example_code.ssm.MaintenanceWindowWrapper.class services: ssm: {CreateOpsItem, CreateMaintenanceWindow, CreateDocument, SendCommand, CommandInvocations, DeleteMaintenanceWindow, UpdateOpsItem} diff --git a/python/example_code/emr/test/test_install_libraries.py b/python/example_code/emr/test/test_install_libraries.py index 67860d3ef3e..dc839c377d3 100644 --- a/python/example_code/emr/test/test_install_libraries.py +++ b/python/example_code/emr/test/test_install_libraries.py @@ -48,11 +48,11 @@ def test_install_libraries_on_core_nodes( runner.add(emr_stubber.stub_list_instances, cluster_id, ["CORE"], instance_ids) for command in commands: runner.add( - ssm_stubber.stub_send_command, instance_ids, [command], command_id + ssm_stubber.stub_send_command, instance_ids, commands=[command], command_id=command_id ) - runner.add(ssm_stubber.stub_list_commands, command_id, status_details) + runner.add(ssm_stubber.stub_list_commands, command_id=command_id, status_details=status_details) if status_details == "InProgress": - runner.add(ssm_stubber.stub_list_commands, command_id, "Success") + runner.add(ssm_stubber.stub_list_commands, command_id=command_id, status_details="Success") elif status_details == "Failed": break diff --git a/python/example_code/ssm/README.md b/python/example_code/ssm/README.md new file mode 100644 index 00000000000..b338289e44d --- /dev/null +++ b/python/example_code/ssm/README.md @@ -0,0 +1,130 @@ +# Systems Manager code examples for the SDK for Python + +## Overview + +Shows how to use the AWS SDK for Python (Boto3) to work with AWS Systems Manager. + + + + +_Systems Manager organizes, monitors, and automates management tasks on your AWS resources._ + +## ⚠ Important + +* Running this code might result in charges to your AWS account. For more details, see [AWS Pricing](https://aws.amazon.com/pricing/) and [Free Tier](https://aws.amazon.com/free/). +* Running the tests might result in charges to your AWS account. +* We recommend that you grant your code least privilege. At most, grant only the minimum permissions required to perform the task. For more information, see [Grant least privilege](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege). +* This code is not tested in every AWS Region. For more information, see [AWS Regional Services](https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services). + + + + +## Code examples + +### Prerequisites + +For prerequisites, see the [README](../../README.md#Prerequisites) in the `python` folder. + +Install the packages required by these examples by running the following in a virtual environment: + +``` +python -m pip install -r requirements.txt +``` + + + + +### Get started + +- [Hello Systems Manager](hello.py#L4) (`listThings`) + + +### Single actions + +Code excerpts that show you how to call individual service functions. + +- [CreateDocument](document.py#L32) +- [CreateMaintenanceWindow](maintenance_window.py#L32) +- [CreateOpsItem](ops_item.py#L34) +- [DeleteDocument](document.py#L56) +- [DeleteMaintenanceWindow](maintenance_window.py#L66) +- [DeleteOpsItem](ops_item.py#L65) +- [DescribeOpsItems](ops_item.py#L13) +- [ListCommands](document.py#L155) +- [SendCommand](document.py#L78) +- [UpdateMaintenanceWindow](maintenance_window.py#L89) +- [UpdateOpsItem](ops_item.py#L117) + +### Scenarios + +Code examples that show you how to accomplish a specific task by calling multiple +functions within the same service. + +- [Get started with Systems Manager](ssm_getting_started.py) + + + + + +## Run the examples + +### Instructions + + + + + +#### Hello Systems Manager + +This example shows you how to get started using Systems Manager. + +``` +python hello.py +``` + + +#### Get started with Systems Manager + +This example shows you how to work with Systems Manager maintenance windows, documents, and OpsItems. + + + + + +Start the example by running the following at a command prompt: + +``` +python ssm_getting_started.py +``` + + + + + +### Tests + +⚠ Running tests might result in charges to your AWS account. + + +To find instructions for running these tests, see the [README](../../README.md#Tests) +in the `python` folder. + + + + + + +## Additional resources + +- [Systems Manager User Guide](https://docs.aws.amazon.com/systems-manager/latest/userguide/what-is-systems-manager.html) +- [Systems Manager API Reference](https://docs.aws.amazon.com/systems-manager/latest/APIReference/Welcome.html) +- [SDK for Python Systems Manager reference](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ssm.html) + + + + +--- + +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 \ No newline at end of file diff --git a/python/example_code/ssm/document.py b/python/example_code/ssm/document.py new file mode 100644 index 00000000000..b1799fa7722 --- /dev/null +++ b/python/example_code/ssm/document.py @@ -0,0 +1,191 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +import logging +import time + +import boto3 +from botocore.exceptions import ClientError + +logger = logging.getLogger(__name__) + + +# snippet-start:[python.example_code.ssm.DocumentWrapper.class] +# snippet-start:[python.example_code.ssm.DocumentWrapper.decl] +class DocumentWrapper: + """Encapsulates AWS Systems Manager Document actions.""" + + def __init__(self, ssm_client): + """ + :param ssm_client: A Boto3 Systems Manager client. + """ + self.ssm_client = ssm_client + self.name = None + + @classmethod + def from_client(cls): + ssm_client = boto3.client("ssm") + return cls(ssm_client) + + # snippet-end:[python.example_code.ssm.DocumentWrapper.decl] + + # snippet-start:[python.example_code.ssm.CreateDocument] + def create(self, content, name): + """ + Creates a document. + + :param content: The content of the document. + :param name: The name of the document. + """ + try: + self.ssm_client.create_document( + Name=name, Content=content, DocumentType="Command" + ) + self.name = name + except ClientError as err: + logger.error( + "Couldn't create %s. Here's why: %s: %s", + name, + err.response["Error"]["Code"], + err.response["Error"]["Message"], + ) + raise + + # snippet-end:[python.example_code.ssm.CreateDocument] + + # snippet-start:[python.example_code.ssm.DeleteDocument] + def delete(self): + """ + Deletes an AWS Systems Manager document. + """ + if self.name is None: + return + + try: + self.ssm_client.delete_document(Name=self.name) + self.name = None + except ClientError as err: + logger.error( + "Couldn't delete %s. Here's why: %s: %s", + self.name, + err.response["Error"]["Code"], + err.response["Error"]["Message"], + ) + raise + + # snippet-end:[python.example_code.ssm.DeleteDocument] + + # snippet-start:[python.example_code.ssm.SendCommand] + def send_command(self, instance_ids): + """ + Sends a command to one or more instances. + + :param instance_ids: The IDs of the instances to send the command to. + :return: The ID of the command. + """ + try: + response = self.ssm_client.send_command( + InstanceIds=instance_ids, DocumentName=self.name, TimeoutSeconds=3600 + ) + return response["Command"]["CommandId"] + except ClientError as err: + logger.error( + "Couldn't send command to %s. Here's why: %s: %s", + self.name, + err.response["Error"]["Code"], + err.response["Error"]["Message"], + ) + raise + + # snippet-end:[python.example_code.ssm.SendCommand] + + # snippet-start:[python.example_code.ssm.DescribeDocument] + def describe(self): + """ + Describes the document. + + :return: Document status. + """ + try: + response = self.ssm_client.describe_document(Name=self.name) + return response["Document"]["Status"] + except ClientError as err: + logger.error( + "Couldn't get %s. Here's why: %s: %s", + self.name, + err.response["Error"]["Code"], + err.response["Error"]["Message"], + ) + raise + + # snippet-end:[python.example_code.ssm.DescribeDocument] + + def wait_until_active(self, max_attempts=20, delay=5): + """ + Waits until the document is active. + + :param max_attempts: The maximum number of attempts for checking the status. + :param delay: The delay in seconds between each check. + """ + attempt = 0 + status = "" + while attempt <= max_attempts: + status = self.describe() + if status == "Active": + break + attempt += 1 + time.sleep(delay) + + if status != "Active": + logger.error("Document is not active.") + else: + logger.info("Document is active.") + + def wait_command_executed(self, command_id, instance_id): + """ + Waits until the command is executed on the instance. + + :param command_id: The ID of the command. + :param instance_id: The ID of the instance. + """ + + waiter = self.ssm_client.get_waiter("command_executed") + waiter.wait(CommandId=command_id, InstanceId=instance_id) + + # snippet-start:[python.example_code.ssm.ListCommands] + def list_commands(self, instance_id): + """ + Lists the commands for an instance. + + :param instance_id: The ID of the instance. + :return: The list of commands. + """ + try: + paginator = self.ssm_client.get_paginator("list_commands") + commands = [] + for page in paginator.paginate(InstanceId=instance_id): + commands.extend(page["Commands"]) + num_of_commands = len(commands) + print(f"{num_of_commands} command(s) found for instance {instance_id}.") + + if num_of_commands > 10: + print("Displaying the first 10 commands:") + num_of_commands = 10 + date_format = "%A, %d %B %Y %I:%M%p" + for command in commands[:num_of_commands]: + print( + f" The time of command invocation is {command['RequestedDateTime'].strftime(date_format)}" + ) + except ClientError as err: + logger.error( + "Couldn't list commands for %s. Here's why: %s: %s", + instance_id, + err.response["Error"]["Code"], + err.response["Error"]["Message"], + ) + raise + + # snippet-end:[python.example_code.ssm.ListCommands] + + +# snippet-end:[python.example_code.ssm.DocumentWrapper.class] diff --git a/python/example_code/ssm/hello.py b/python/example_code/ssm/hello.py new file mode 100644 index 00000000000..5f177f55370 --- /dev/null +++ b/python/example_code/ssm/hello.py @@ -0,0 +1,29 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +# snippet-start:[python.example_code.ssm.Hello] +import boto3 + + +def hello_systems_manager(ssm_client): + """ + Use the AWS SDK for Python (Boto3) to create an AWS Systems Manager + client and list the first 5 documents in your account. + This example uses the default settings specified in your shared credentials + and config files. + + :param ssm_client: A Boto3 AWS Systems Manager Client object. This object wraps + the low-level AWS Systems Manager service API. + """ + print("Hello, AWS Systems Manager! Let's list some of your documents:\n") + + paginator = ssm_client.get_paginator("list_documents") + page_iterator = paginator.paginate(PaginationConfig={"MaxItems": 5}) + for page in page_iterator: + for document in page["DocumentIdentifiers"]: + print(f" {document['Name']}") + + +if __name__ == "__main__": + hello_systems_manager(boto3.client("ssm")) +# snippet-end:[python.example_code.ssm.Hello] diff --git a/python/example_code/ssm/maintenance_window.py b/python/example_code/ssm/maintenance_window.py new file mode 100644 index 00000000000..75edca644e8 --- /dev/null +++ b/python/example_code/ssm/maintenance_window.py @@ -0,0 +1,126 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +import logging + +import boto3 +from botocore.exceptions import ClientError + +logger = logging.getLogger(__name__) + + +# snippet-start:[python.example_code.ssm.MaintenanceWindowWrapper.class] +# snippet-start:[python.example_code.ssm.MaintenanceWindowWrapper.decl] +class MaintenanceWindowWrapper: + """Encapsulates AWS Systems Manager maintenance window actions.""" + + def __init__(self, ssm_client): + """ + :param ssm_client: A Boto3 Systems Manager client. + """ + self.ssm_client = ssm_client + self.window_id = None + self.name = None + + @classmethod + def from_client(cls): + ssm_client = boto3.client("ssm") + return cls(ssm_client) + + # snippet-end:[python.example_code.ssm.MaintenanceWindowWrapper.decl] + + # snippet-start:[python.example_code.ssm.CreateMaintenanceWindow] + def create(self, name, schedule, duration, cutoff, allow_unassociated_targets): + """ + Create an AWS Systems Manager maintenance window. + + :param name: The name of the maintenance window. + :param schedule: The schedule of the maintenance window. + :param duration: The duration of the maintenance window. + :param cutoff: The cutoff time of the maintenance window. + :param allow_unassociated_targets: Allow the maintenance window to run on managed nodes, even + if you haven't registered those nodes as targets. + """ + try: + response = self.ssm_client.create_maintenance_window( + Name=name, + Schedule=schedule, + Duration=duration, + Cutoff=cutoff, + AllowUnassociatedTargets=allow_unassociated_targets, + ) + self.window_id = response["WindowId"] + self.name = name + logger.info("Created maintenance window %s.", self.window_id) + except ClientError as err: + logger.error( + "Couldn't create maintenance window %s. Here's why: %s: %s", + name, + err.response["Error"]["Code"], + err.response["Error"]["Message"], + ) + raise + + # snippet-end:[python.example_code.ssm.CreateMaintenanceWindow] + + # snippet-start:[python.example_code.ssm.DeleteMaintenanceWindow] + def delete(self): + """ + Delete the associated AWS Systems Manager maintenance window. + """ + if self.window_id is None: + return + + try: + self.ssm_client.delete_maintenance_window(WindowId=self.window_id) + logger.info("Deleted maintenance window %s.", self.window_id) + self.window_id = None + except ClientError as err: + logger.error( + "Couldn't delete maintenance window %s. Here's why: %s: %s", + self.window_id, + err.response["Error"]["Code"], + err.response["Error"]["Message"], + ) + raise + + # snippet-end:[python.example_code.ssm.DeleteMaintenanceWindow] + + # snippet-start:[python.example_code.ssm.UpdateMaintenanceWindow] + def update( + self, name, enabled, schedule, duration, cutoff, allow_unassociated_targets + ): + """ + Update an AWS Systems Manager maintenance window. + + :param name: The name of the maintenance window. + :param enabled: Whether the maintenance window is enabled to run on managed nodes. + :param schedule: The schedule of the maintenance window. + :param duration: The duration of the maintenance window. + :param cutoff: The cutoff time of the maintenance window. + :param allow_unassociated_targets: Allow the maintenance window to run on managed nodes, even + if you haven't registered those nodes as targets. + """ + try: + self.ssm_client.update_maintenance_window( + WindowId=self.window_id, + Name=name, + Enabled=enabled, + Schedule=schedule, + Duration=duration, + Cutoff=cutoff, + AllowUnassociatedTargets=allow_unassociated_targets, + ) + self.name = name + logger.info("Updated maintenance window %s.", self.window_id) + except ClientError as err: + logger.error( + "Couldn't update maintenance window %s. Here's why: %s: %s", + self.name, + err.response["Error"]["Code"], + err.response["Error"]["Message"], + ) + raise + + # snippet-end:[python.example_code.ssm.UpdateMaintenanceWindow] + # snippet-end:[python.example_code.ssm.MaintenanceWindowWrapper.class] diff --git a/python/example_code/ssm/ops_item.py b/python/example_code/ssm/ops_item.py new file mode 100644 index 00000000000..c1fd2d19c68 --- /dev/null +++ b/python/example_code/ssm/ops_item.py @@ -0,0 +1,148 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +import logging + +import boto3 +from botocore.exceptions import ClientError + +logger = logging.getLogger(__name__) + + +# snippet-start:[python.example_code.ssm.OpsItemWrapper.class] +# snippet-start:[python.example_code.ssm.OpsItemWrapper.decl] +class OpsItemWrapper: + """Encapsulates AWS Systems Manager OpsItem actions.""" + + def __init__(self, ssm_client): + """ + :param ssm_client: A Boto3 Systems Manager client. + """ + self.ssm_client = ssm_client + self.id = None + + @classmethod + def from_client(cls): + """ + :return: A OpsItemWrapper instance. + """ + ssm_client = boto3.client("ssm") + return cls(ssm_client) + + # snippet-end:[python.example_code.ssm.OpsItemWrapper.decl] + + # snippet-start:[python.example_code.ssm.CreateOpsItem] + def create(self, title, source, category, severity, description): + """ + Create an OpsItem + + :param title: The OpsItem title. + :param source: The OpsItem source. + :param category: The OpsItem category. + :param severity: The OpsItem severity. + :param description: The OpsItem description. + + """ + try: + response = self.ssm_client.create_ops_item( + Title=title, + Source=source, + Category=category, + Severity=severity, + Description=description, + ) + self.id = response["OpsItemId"] + except ClientError as err: + logger.error( + "Couldn't create ops item %s. Here's why: %s: %s", + title, + err.response["Error"]["Code"], + err.response["Error"]["Message"], + ) + raise + # snippet-end:[python.example_code.ssm.CreateOpsItem] + + # snippet-start:[python.example_code.ssm.DeleteOpsItem] + def delete(self): + """ + Delete the OpsItem. + """ + if self.id is None: + return + try: + self.ssm_client.delete_ops_item(OpsItemId=self.id) + self.id = None + except ClientError as err: + logger.error( + "Couldn't delete ops item %s. Here's why: %s: %s", + self.id, + err.response["Error"]["Code"], + err.response["Error"]["Message"], + ) + raise + + # snippet-end:[python.example_code.ssm.DeleteOpsItem] + + # snippet-start:[python.example_code.ssm.DescribeOpsItem] + def describe(self): + """ + Describe an OpsItem. + """ + try: + paginator = self.ssm_client.get_paginator("describe_ops_items") + ops_items = [] + for page in paginator.paginate( + OpsItemFilters=[ + {"Key": "OpsItemId", "Values": [self.id], "Operator": "Equal"} + ] + ): + ops_items.extend(page["OpsItemSummaries"]) + + print(f"{len(ops_items)} ops item(s) found.") + for item in ops_items: + print( + f"The item title is {item['Title']} and the status is {item['Status']}" + ) + except ClientError as err: + logger.error( + "Couldn't describe ops item %s. Here's why: %s: %s", + self.id, + err.response["Error"]["Code"], + err.response["Error"]["Message"], + ) + raise + + # snippet-end:[python.example_code.ssm.DescribeOpsItem] + + # snippet-start:[python.example_code.ssm.UpdateOpsItem] + def update(self, title=None, description=None, status=None): + """ + Update an OpsItem. + + :param title: The new OpsItem title. + :param description: The new OpsItem description. + :param status: The new OpsItem status. + :return: + """ + args = dict(OpsItemId=self.id) + if title is not None: + args["Title"] = title + if description is not None: + args["Description"] = description + if status is not None: + args["Status"] = status + try: + self.ssm_client.update_ops_item(**args) + except ClientError as err: + logger.error( + "Couldn't update ops item %s. Here's why: %s: %s", + self.id, + err.response["Error"]["Code"], + err.response["Error"]["Message"], + ) + raise + + # snippet-end:[python.example_code.ssm.UpdateOpsItem] + + +# snippet-end:[python.example_code.ssm.OpsItemWrapper.class] diff --git a/python/example_code/ssm/requirements.txt b/python/example_code/ssm/requirements.txt new file mode 100644 index 00000000000..29b7d936776 --- /dev/null +++ b/python/example_code/ssm/requirements.txt @@ -0,0 +1,3 @@ +boto3>=1.34.0 +botocore>=1.34.0 +pytest>=7.4.0 diff --git a/python/example_code/ssm/ssm_getting_started.py b/python/example_code/ssm/ssm_getting_started.py new file mode 100644 index 00000000000..1275764c050 --- /dev/null +++ b/python/example_code/ssm/ssm_getting_started.py @@ -0,0 +1,235 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +""" +Purpose + +Shows how to use the AWS SDK for Python (Boto3) with AWS Systems Manager to do the following: + +* Create an AWS Systems Manager maintenance window with a user-provided name. +* Modify the maintenance window schedule. +* Create a Systems Manager document with a user-provided name. +* Send a command to a specified EC2 instance using the created Systems Manager document and display the time when + the command was invoked. +* Create a Systems Manager OpsItem with a predefined title, source, category, and severity. +* Update and resolve the created OpsItem. +* Delete the Systems Manager maintenance window, OpsItem, and document. +""" + +import logging +import sys + +from document import DocumentWrapper +from maintenance_window import MaintenanceWindowWrapper +from ops_item import OpsItemWrapper + +# Add relative path to include demo_tools in this code example without need for setup. +sys.path.append("../..") +import demo_tools.question as q + +logger = logging.getLogger(__name__) + + +# snippet-start:[python.example_code.ssm.Scenario_GetStartedSSM] +class SystemsManagerScenario: + """Runs an interactive scenario that shows how to get started using Amazon Systems Manager.""" + + def __init__(self, document_wrapper, maintenance_window_wrapper, ops_item_wrapper): + """ + :param document_wrapper: An object that wraps Systems Manager document functions. + :param maintenance_window_wrapper: An object that wraps Systems Manager maintenance window functions. + :param ops_item_wrapper: An object that wraps Systems Manager OpsItem functions. + """ + self.document_wrapper = document_wrapper + self.maintenance_window_wrapper = maintenance_window_wrapper + self.ops_item_wrapper = ops_item_wrapper + + def run(self): + """Demonstrates how to use the AWS SDK for Python (Boto3) to get started with Systems Manager.""" + try: + print("-" * 88) + print( + """ +Welcome to the AWS Systems Manager SDK Getting Started scenario. +This program demonstrates how to interact with Systems Manager using the AWS SDK for Python (Boto3). +Systems Manager is the operations hub for your AWS applications and resources and a secure end-to-end management +solution. The program's primary functions include creating a maintenance window, creating a document, sending a +command to a document, listing documents, listing commands, creating an OpsItem, modifying an OpsItem, and deleting +Systems Manager resources. Upon completion of the program, all AWS resources are cleaned up. +Let's get started...""" + ) + q.ask("Please hit Enter") + + print("-" * 88) + print("Create a Systems Manager maintenance window.") + maintenance_window_name = q.ask( + "Please enter the maintenance window name (default is ssm-maintenance-window):", + q.non_empty, + ) + + self.maintenance_window_wrapper.create( + name=maintenance_window_name, + schedule="cron(0 10 ? * MON-FRI *)", + duration=2, + cutoff=1, + allow_unassociated_targets=True, + ) + + print("-" * 88) + print("Modify the maintenance window by changing the schedule") + q.ask("Please hit Enter") + + self.maintenance_window_wrapper.update( + name=maintenance_window_name, + schedule="cron(0 0 ? * MON *)", + duration=24, + cutoff=1, + allow_unassociated_targets=True, + enabled=True, + ) + + print("-" * 88) + print( + "Create a document that defines the actions that Systems Manager performs on your EC2 instance." + ) + document_name = q.ask( + "Please enter the document name (default is ssmdocument):", q.non_empty + ) + + self.document_wrapper.create( + name=document_name, + content=""" +{ + "schemaVersion": "2.2", + "description": "Run a simple shell command", + "mainSteps": [ + { + "action": "aws:runShellScript", + "name": "runEchoCommand", + "inputs": { + "runCommand": [ + "echo 'Hello, world!'" + ] + } + } + ] +} + """, + ) + + self.document_wrapper.wait_until_active() + + print( + """ +Now you have the option of running a command on an EC2 instance that echoes 'Hello, world!'. +In order to run this command, you must provide the instance ID of a Linux EC2 instance. In other +words, an instance that can run a shell script containing the echo command. For information about creating an EC2 +instance, see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-launch-instance-wizard.html. + """ + ) + + if q.ask( + "Would you like to run a command on an EC2 instance? (y/n)", + q.is_yesno, + ): + instance_id = q.ask( + "Please enter the instance ID of the EC2 instance:", q.non_empty + ) + command_id = self.document_wrapper.send_command( + instance_ids=[instance_id] + ) + + self.document_wrapper.wait_command_executed( + command_id=command_id, instance_id=instance_id + ) + + print("-" * 88) + print( + "Lets get the time when the specific command was sent to the specific managed node" + ) + q.ask("Please hit Enter") + + self.document_wrapper.list_commands(instance_id=instance_id) + + print("-" * 88) + print("-" * 88) + print( + """ +Now we will create a Systems Manager OpsItem. +An OpsItem is a feature provided by the Systems Manager service. +It is a type of operational data item that allows you to manage and track various operational issues, +events, or tasks within your AWS environment. + +You can create OpsItems to track and manage operational issues as they arise. +For example, you could create an OpsItem whenever your application detects a critical error +or an anomaly in your infrastructure. + """ + ) + q.ask("Please hit Enter") + + self.ops_item_wrapper.create( + title="Disk Space Alert", + description="Created by the Systems Manager Python (Boto3) API", + source="EC2", + category="Performance", + severity="2", + ) + + print("-" * 88) + print("-" * 88) + print(f"Now we will update the OpsItem {self.ops_item_wrapper.id}") + q.ask("Please hit Enter") + + self.ops_item_wrapper.update( + title="Disk Space Alert", + description=f"An update to {self.ops_item_wrapper.id}", + ) + + print( + f"Now we will get the status of the OpsItem {self.ops_item_wrapper.id}" + ) + q.ask("Please hit Enter") + + self.ops_item_wrapper.describe() + + print(f"Now we will resolve the OpsItem {self.ops_item_wrapper.id}") + q.ask("Please hit Enter") + + self.ops_item_wrapper.update(status="Resolved") + + print("-" * 88) + print("-" * 88) + if q.ask( + "Would you like to delete the Systems Manager resources? (y/n)", + q.is_yesno, + ): + print("You selected to delete the resources.") + self.cleanup() + else: + print("The Systems Manager resources will not be deleted") + + print("-" * 88) + print("This concludes the Systems Manager SDK Getting Started scenario.") + print("-" * 88) + + except Exception: + self.cleanup() + raise + + def cleanup(self): + self.maintenance_window_wrapper.delete() + self.ops_item_wrapper.delete() + self.document_wrapper.delete() + + +if __name__ == "__main__": + try: + scenario = SystemsManagerScenario( + DocumentWrapper.from_client(), + MaintenanceWindowWrapper.from_client(), + OpsItemWrapper.from_client(), + ) + scenario.run() + except Exception: + logging.exception("Something went wrong with the demo.") +# snippet-end:[python.example_code.ssm.Scenario_GetStartedSSM] diff --git a/python/example_code/ssm/test/conftest.py b/python/example_code/ssm/test/conftest.py new file mode 100644 index 00000000000..d814c84a937 --- /dev/null +++ b/python/example_code/ssm/test/conftest.py @@ -0,0 +1,12 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +""" +Contains common test fixtures used to run Amazon EMR tests. +""" + +import sys + +# This is needed so Python can find test_tools on the path. +sys.path.append("../..") +from test_tools.fixtures.common import * diff --git a/python/example_code/ssm/test/test_document.py b/python/example_code/ssm/test/test_document.py new file mode 100644 index 00000000000..c6f6471d6c5 --- /dev/null +++ b/python/example_code/ssm/test/test_document.py @@ -0,0 +1,148 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +""" +Unit tests for document.py functions. +""" + +import boto3 +import pytest +from botocore.exceptions import ClientError + +from document import DocumentWrapper + + +@pytest.mark.parametrize("error_code", [None, "TestException"]) +def test_create(make_stubber, error_code): + ssm_client = boto3.client("ssm") + ssm_stubber = make_stubber(ssm_client) + document_wrapper = DocumentWrapper(ssm_client) + name = "python-test" + content = """ +{ + "schemaVersion": "2.2", + "description": "Run a simple shell command", + "mainSteps": [ + { + "action": "aws:runShellScript", + "name": "runEchoCommand", + "inputs": { + "runCommand": [ + "echo 'Hello, world!'" + ] + } + } + ] +} + """ + + ssm_stubber.stub_create_document( + content, + name, + error_code=error_code, + ) + + if error_code is None: + document_wrapper.create( + content, + name, + ) + assert document_wrapper.name == name + else: + with pytest.raises(ClientError) as exc_info: + document_wrapper.create( + content, + name, + ) + assert exc_info.value.response["Error"]["Code"] == error_code + + +@pytest.mark.parametrize("error_code", [None, "TestException"]) +def test_delete(make_stubber, error_code): + ssm_client = boto3.client("ssm") + ssm_stubber = make_stubber(ssm_client) + document_wrapper = DocumentWrapper(ssm_client) + name = "python-test" + document_wrapper.name = name + + ssm_stubber.stub_delete_document( + name, + error_code=error_code, + ) + + if error_code is None: + document_wrapper.delete() + assert document_wrapper.name is None + else: + with pytest.raises(ClientError) as exc_info: + document_wrapper.delete() + assert exc_info.value.response["Error"]["Code"] == error_code + + +@pytest.mark.parametrize("error_code", [None, "TestException"]) +def test_describe_document(make_stubber, error_code): + ssm_client = boto3.client("ssm") + ssm_stubber = make_stubber(ssm_client) + document_wrapper = DocumentWrapper(ssm_client) + name = "python-test" + document_wrapper.name = name + + ssm_stubber.stub_describe_document( + name, + error_code=error_code, + ) + + if error_code is None: + status = document_wrapper.describe() + assert status == "Active" + else: + with pytest.raises(ClientError) as exc_info: + document_wrapper.describe() + assert exc_info.value.response["Error"]["Code"] == error_code + + +@pytest.mark.parametrize("error_code", [None, "TestException"]) +def test_list_commands(make_stubber, error_code): + ssm_client = boto3.client("ssm") + ssm_stubber = make_stubber(ssm_client) + document_wrapper = DocumentWrapper(ssm_client) + instance_id = "XXXXXXXXXXXX" + + ssm_stubber.stub_list_commands( + instance_id=instance_id, + error_code=error_code, + ) + + if error_code is None: + document_wrapper.list_commands(instance_id) + + else: + with pytest.raises(ClientError) as exc_info: + document_wrapper.list_commands(instance_id) + assert exc_info.value.response["Error"]["Code"] == error_code + + +@pytest.mark.parametrize("error_code", [None, "TestException"]) +def test_send_command(make_stubber, error_code): + ssm_client = boto3.client("ssm") + ssm_stubber = make_stubber(ssm_client) + document_wrapper = DocumentWrapper(ssm_client) + name = "python-test" + document_wrapper.name = name + instance_ids = ["i-0123456789abcdef"] + command_id = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" + + ssm_stubber.stub_send_command( + instance_ids, + document_name=name, + command_id=command_id, + error_code=error_code, + ) + + if error_code is None: + response = document_wrapper.send_command(instance_ids) + assert response == command_id + else: + with pytest.raises(ClientError) as exc_info: + document_wrapper.send_command(instance_ids) + assert exc_info.value.response["Error"]["Code"] == error_code diff --git a/python/example_code/ssm/test/test_maintenance_window.py b/python/example_code/ssm/test/test_maintenance_window.py new file mode 100644 index 00000000000..8bf970434e9 --- /dev/null +++ b/python/example_code/ssm/test/test_maintenance_window.py @@ -0,0 +1,117 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +""" +Unit tests for maintenance_window.py functions. +""" + +import boto3 +import pytest +from botocore.exceptions import ClientError + +from maintenance_window import MaintenanceWindowWrapper + + +@pytest.mark.parametrize("error_code", [None, "TestException"]) +def test_create(make_stubber, error_code): + ssm_client = boto3.client("ssm") + ssm_stubber = make_stubber(ssm_client) + maintenance_window_wrapper = MaintenanceWindowWrapper(ssm_client) + name = "python-test" + schedule = "cron(0 10 ? * MON-FRI *)" + duration = 2 + cutoff = 1 + allow_unassociated_targets = True + window_id = "mw-0123456789abcdef0" + + ssm_stubber.stub_create_maintenance_window( + name=name, + window_id=window_id, + allow_unassociated_targets=allow_unassociated_targets, + cutoff=cutoff, + duration=duration, + schedule=schedule, + error_code=error_code, + ) + + if error_code is None: + maintenance_window_wrapper.create( + name, schedule, duration, cutoff, allow_unassociated_targets + ) + assert maintenance_window_wrapper.window_id == window_id + assert maintenance_window_wrapper.name == name + else: + with pytest.raises(ClientError) as exc_info: + maintenance_window_wrapper.create( + name, schedule, duration, cutoff, allow_unassociated_targets + ) + assert exc_info.value.response["Error"]["Code"] == error_code + + +@pytest.mark.parametrize("error_code", [None, "TestException"]) +def test_delete_maintenance_window(make_stubber, error_code): + ssm_client = boto3.client("ssm") + ssm_stubber = make_stubber(ssm_client) + maintenance_window_wrapper = MaintenanceWindowWrapper(ssm_client) + window_id = "mw-0123456789abcdef0" + maintenance_window_wrapper.window_id = window_id + + ssm_stubber.stub_delete_maintenance_window( + window_id, + error_code=error_code, + ) + + if error_code is None: + maintenance_window_wrapper.delete() + assert maintenance_window_wrapper.window_id is None + else: + with pytest.raises(ClientError) as exc_info: + maintenance_window_wrapper.delete() + assert exc_info.value.response["Error"]["Code"] == error_code + + +@pytest.mark.parametrize("error_code", [None, "TestException"]) +def test_update_maintenance_window(make_stubber, error_code): + ssm_client = boto3.client("ssm") + ssm_stubber = make_stubber(ssm_client) + maintenance_window_wrapper = MaintenanceWindowWrapper(ssm_client) + window_id = "mw-0123456789abcdef0" + maintenance_window_wrapper.window_id = window_id + new_name = "updated-python-test" + new_schedule = "cron(0 12 ? * MON-FRI *)" + enabled = True + duration = 4 + cutoff = 2 + allow_unassociated_targets = False + + ssm_stubber.stub_update_maintenance_window( + window_id, + name=new_name, + schedule=new_schedule, + enabled=enabled, + duration=duration, + cutoff=cutoff, + allow_unassociated_targets=allow_unassociated_targets, + error_code=error_code, + ) + + if error_code is None: + maintenance_window_wrapper.update( + name=new_name, + enabled=enabled, + schedule=new_schedule, + duration=duration, + cutoff=cutoff, + allow_unassociated_targets=allow_unassociated_targets, + ) + else: + with pytest.raises(ClientError) as exc_info: + maintenance_window_wrapper.update( + name=new_name, + enabled=enabled, + schedule=new_schedule, + duration=duration, + cutoff=cutoff, + allow_unassociated_targets=allow_unassociated_targets, + ) + assert exc_info.value.response["Error"]["Code"] == error_code diff --git a/python/example_code/ssm/test/test_ops_item.py b/python/example_code/ssm/test/test_ops_item.py new file mode 100644 index 00000000000..52e48e1c522 --- /dev/null +++ b/python/example_code/ssm/test/test_ops_item.py @@ -0,0 +1,114 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +""" +Unit tests for maintenance_window.py functions. +""" + +import boto3 +import pytest +from botocore.exceptions import ClientError + +from ops_item import OpsItemWrapper + + +@pytest.mark.parametrize("error_code", [None, "TestException"]) +def test_create_ops_item(make_stubber, error_code): + ssm_client = boto3.client("ssm") + ssm_stubber = make_stubber(ssm_client) + ops_item_wrapper = OpsItemWrapper(ssm_client) + title = "Test OpsItem" + description = "This is a test OpsItem" + source = "test-source" + severity = "2" + category = "Availability" + + ops_item_id = "oi-0123456789abcdef" + + ssm_stubber.stub_create_ops_item( + title, + source, + category, + severity, + description, + ops_item_id, + error_code=error_code, + ) + + if error_code is None: + ops_item_wrapper.create(title, source, category, severity, description) + assert ops_item_wrapper.id == ops_item_id + else: + with pytest.raises(ClientError) as exc_info: + ops_item_wrapper.create(title, source, category, severity, description) + assert exc_info.value.response["Error"]["Code"] == error_code + + +@pytest.mark.parametrize("error_code", [None, "TestException"]) +def test_delete_ops_item(make_stubber, error_code): + ssm_client = boto3.client("ssm") + ssm_stubber = make_stubber(ssm_client) + ops_item_wrapper = OpsItemWrapper(ssm_client) + ops_item_id = "oi-0123456789abcdef0" + ops_item_wrapper.id = ops_item_id + + ssm_stubber.stub_delete_ops_item(ops_item_id, error_code=error_code) + + if error_code is None: + ops_item_wrapper.delete() + assert ops_item_wrapper.id is None + else: + with pytest.raises(ClientError) as exc_info: + ops_item_wrapper.delete() + assert exc_info.value.response["Error"]["Code"] == error_code + + +@pytest.mark.parametrize("error_code", [None, "TestException"]) +def test_describe_ops_item(make_stubber, error_code): + ssm_client = boto3.client("ssm") + ssm_stubber = make_stubber(ssm_client) + ops_item_wrapper = OpsItemWrapper(ssm_client) + ops_item_id = "oi-0123456789abcdef0" + ops_item_wrapper.id = ops_item_id + + filters = [{"Key": "OpsItemId", "Operator": "Equal", "Values": [ops_item_id]}] + + ssm_stubber.stub_describe_ops_items(filters=filters, error_code=error_code) + + if error_code is None: + ops_item_wrapper.describe() + else: + with pytest.raises(ClientError) as exc_info: + ops_item_wrapper.describe() + assert exc_info.value.response["Error"]["Code"] == error_code + + +@pytest.mark.parametrize("error_code", [None, "TestException"]) +def test_update_ops_item(make_stubber, error_code): + ssm_client = boto3.client("ssm") + ssm_stubber = make_stubber(ssm_client) + ops_item_wrapper = OpsItemWrapper(ssm_client) + ops_item_id = "oi-0123456789abcdef0" + ops_item_wrapper.id = ops_item_id + new_title = "Updated OpsItem" + new_description = "This is an updated OpsItem." + status = "Completed" + + ssm_stubber.stub_update_ops_item( + ops_item_id, + title=new_title, + description=new_description, + status=status, + error_code=error_code, + ) + + if error_code is None: + ops_item_wrapper.update( + title=new_title, description=new_description, status=status + ) + else: + with pytest.raises(ClientError) as exc_info: + ops_item_wrapper.update( + title=new_title, description=new_description, status=status + ) + assert exc_info.value.response["Error"]["Code"] == error_code diff --git a/python/example_code/ssm/test/test_ssm_getting_started.py b/python/example_code/ssm/test/test_ssm_getting_started.py new file mode 100644 index 00000000000..c926aa76a38 --- /dev/null +++ b/python/example_code/ssm/test/test_ssm_getting_started.py @@ -0,0 +1,43 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +import pytest + +from document import DocumentWrapper +from maintenance_window import MaintenanceWindowWrapper +from ops_item import OpsItemWrapper +from ssm_getting_started import SystemsManagerScenario + + +@pytest.fixture +def mock_wait(monkeypatch): + return + + +@pytest.mark.integ +def test_run_ssm_scenario_integ(input_mocker, capsys): + scenario = SystemsManagerScenario( + DocumentWrapper.from_client(), + MaintenanceWindowWrapper.from_client(), + OpsItemWrapper.from_client(), + ) + + input_mocker.mock_answers( + [ + "", # Please hit Enter. + "python-scenario-test", # Please enter the maintenance window name (default is ssm-maintenance-window):. + "", # Please hit Enter. + "python-scenario-test", # Please enter the document name (default is ssmdocument). + "n", # Would you like to run a command on an EC2 instance? + "", # Please hit Enter. + "", # Please hit Enter. + "", # Please hit Enter. + "", # Please hit Enter. + "y", # Would you like to delete the Systems Manager resources? (y/n). + ] + ) + + scenario.run() + + capt = capsys.readouterr() + assert "This concludes the Systems Manager SDK Getting Started scenario" in capt.out diff --git a/python/test_tools/ssm_stubber.py b/python/test_tools/ssm_stubber.py index 1f237b88be5..c837f00cfcf 100644 --- a/python/test_tools/ssm_stubber.py +++ b/python/test_tools/ssm_stubber.py @@ -8,6 +8,8 @@ set up stubs and passes all calls through to the Boto3 client. """ +from datetime import datetime + from botocore.stub import ANY from test_tools.example_stubber import ExampleStubber @@ -34,15 +36,19 @@ def __init__(self, client, use_stubs=True): super().__init__(client, use_stubs) def stub_send_command( - self, instance_ids, commands, command_id=None, timeout=3600, error_code=None + self, instance_ids, commands=None, document_name="AWS-RunShellScript", command_id=None, timeout=3600, + error_code=None ): expected_parameters = { "InstanceIds": instance_ids, - "DocumentName": "AWS-RunShellScript", - "Parameters": {"commands": commands}, + "DocumentName": document_name, } + if commands: + expected_parameters["Parameters"] = {"commands": commands} + if timeout is not None: expected_parameters["TimeoutSeconds"] = timeout + response = {} if command_id is not None: response["Command"] = {"CommandId": command_id} @@ -50,10 +56,27 @@ def stub_send_command( "send_command", expected_parameters, response, error_code=error_code ) - def stub_list_commands(self, command_id, status_details, error_code=None): - expected_parameters = {"CommandId": command_id} + def stub_list_commands(self, command_id=None, instance_id=None, status_details=None, + error_code=None): + expected_parameters = {} + if instance_id is not None: + expected_parameters["InstanceId"] = instance_id + if command_id is not None: + expected_parameters["CommandId"] = command_id + + command_response = {"RequestedDateTime": datetime.now()} + + if status_details is not None: + command_response["StatusDetails"] = status_details + + if command_id is not None: + command_response["CommandId"] = command_id + + if instance_id is not None: + command_response["InstanceIds"] = [instance_id] + response = { - "Commands": [{"CommandId": command_id, "StatusDetails": status_details}] + "Commands": [command_response] } self._stub_bifurcator( "list_commands", expected_parameters, response, error_code=error_code @@ -84,16 +107,200 @@ def stub_put_parameter(self, name, value, error_code=None): "put_parameter", expected_params, response, error_code=error_code ) - def stub_describe_instance_information(self, instance_ids, error_code=None): - expected_params = {} + def stub_create_document(self, content, name, error_code=None): + expected_params = {"Name": name, + "Content": content, + 'DocumentType': 'Command' + } + response = {} + self._stub_bifurcator( + "create_document", + expected_params, + response, + error_code=error_code, + ) + + def stub_delete_document(self, name, error_code=None): + expected_params = { + "Name": name + } + response = {} + self._stub_bifurcator( + "delete_document", + expected_params, + response, + error_code=error_code + ) + + def stub_describe_document(self, name, error_code=None): + expected_params = { + "Name": name + } + + response = { + "Document": { + "Status": "Active", + } + } + + self._stub_bifurcator( + "describe_document", + expected_params, + response, + error_code=error_code + ) + + def stub_create_maintenance_window(self, name, window_id, allow_unassociated_targets=False, cutoff=2, duration=2, + schedule="cron(0 0 ? * MON *)", error_code=None): + expected_params = { + "Name": name, + "AllowUnassociatedTargets": allow_unassociated_targets, + "Cutoff": cutoff, + "Duration": duration, + "Schedule": schedule + } + + response = { + "WindowId": window_id + } + + self._stub_bifurcator( + "create_maintenance_window", + expected_params, + response, + error_code=error_code + ) + + def stub_delete_maintenance_window(self, window_id, error_code=None): + expected_params = { + "WindowId": window_id + } + + response = {} + + self._stub_bifurcator( + "delete_maintenance_window", + expected_params, + response, + error_code=error_code + ) + + def stub_update_maintenance_window(self, window_id, name=None, allow_unassociated_targets=None, cutoff=None, + duration=None, enabled=None, schedule=None, error_code=None): + expected_params = { + "WindowId": window_id + } + + if name is not None: + expected_params["Name"] = name + if allow_unassociated_targets is not None: + expected_params["AllowUnassociatedTargets"] = allow_unassociated_targets + if cutoff is not None: + expected_params["Cutoff"] = cutoff + if duration is not None: + expected_params["Duration"] = duration + if enabled is not None: + expected_params["Enabled"] = enabled + if schedule is not None: + expected_params["Schedule"] = schedule + + response = { + "WindowId": window_id + } + + self._stub_bifurcator( + "update_maintenance_window", + expected_params, + response, + error_code=error_code + ) + + def stub_create_ops_item(self, title, source, category, severity, description, ops_item_id, error_code=None): + expected_params = { + "Title": title, + "Source": source, + "Category": category, + "Severity": severity, + "Description": description + } + + response = { + "OpsItemId": ops_item_id + } + + self._stub_bifurcator( + "create_ops_item", + expected_params, + response, + error_code=error_code + ) + + def stub_delete_ops_item(self, ops_item_id, error_code=None): + expected_params = { + "OpsItemId": ops_item_id + } + + response = {} + + self._stub_bifurcator( + "delete_ops_item", + expected_params, + response, + error_code=error_code + ) + + def stub_describe_ops_items(self, filters, error_code=None): + expected_params = {"OpsItemFilters": filters} + response = { - "InstanceInformationList": [ - {"InstanceId": instance_id} for instance_id in instance_ids + "OpsItemSummaries": [ + { + "OpsItemId": "oi-0123456789abcdef0", + "Title": "Test OpsItem", + "Source": "test-source", + "CreatedBy": "test-user", + "CreatedTime": datetime.now(), + "LastModifiedTime": datetime.now(), + "Status": "Open", + "Severity": "High", + "Category": "Availability" + } ] } + self._stub_bifurcator( - "describe_instance_information", + "describe_ops_items", expected_params, response, - error_code=error_code, + error_code=error_code + ) + + def stub_update_ops_item(self, ops_item_id, title=None, description=None, source=None, severity=None, + status=None, category=None, notifications=None, error_code=None): + expected_params = { + "OpsItemId": ops_item_id + } + + if title is not None: + expected_params["Title"] = title + if description is not None: + expected_params["Description"] = description + if source is not None: + expected_params["Source"] = source + if severity is not None: + expected_params["Severity"] = severity + if status is not None: + expected_params["Status"] = status + if category is not None: + expected_params["Category"] = category + if notifications is not None: + expected_params["Notifications"] = notifications + + response = {} + + self._stub_bifurcator( + "update_ops_item", + expected_params, + response, + error_code=error_code )