|
| 1 | +# |
| 2 | +# Copyright 2024 Splunk Inc. |
| 3 | +# |
| 4 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | +# you may not use this file except in compliance with the License. |
| 6 | +# You may obtain a copy of the License at |
| 7 | +# |
| 8 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | +# |
| 10 | +# Unless required by applicable law or agreed to in writing, software |
| 11 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | +# See the License for the specific language governing permissions and |
| 14 | +# limitations under the License. |
| 15 | +# |
| 16 | + |
| 17 | +from solnlib import splunk_rest_client as rest_client |
| 18 | +from typing import Optional, List |
| 19 | +import json |
| 20 | + |
| 21 | +__all__ = ["BulletinRestClient"] |
| 22 | + |
| 23 | + |
| 24 | +class BulletinRestClient: |
| 25 | + """REST client for handling Bulletin messages.""" |
| 26 | + |
| 27 | + MESSAGES_ENDPOINT = "/services/messages" |
| 28 | + |
| 29 | + headers = [("Content-Type", "application/json")] |
| 30 | + |
| 31 | + class Severity: |
| 32 | + INFO = "info" |
| 33 | + WARNING = "warn" |
| 34 | + ERROR = "error" |
| 35 | + |
| 36 | + def __init__( |
| 37 | + self, |
| 38 | + message_name: str, |
| 39 | + session_key: str, |
| 40 | + app: str, |
| 41 | + **context: dict, |
| 42 | + ): |
| 43 | + """Initializes BulletinRestClient. |
| 44 | + When creating a new bulletin message, you must provide a name, which is a kind of ID. |
| 45 | + If you try to create another message with the same name (ID), the API will not add another message |
| 46 | + to the bulletin, but it will overwrite the existing one. Similar behaviour applies to deletion. |
| 47 | + To delete a message, you must indicate the name (ID) of the message. |
| 48 | + To provide better and easier control over bulletin messages, this client works in such a way |
| 49 | + that there is one instance responsible for handling one specific message. |
| 50 | + If you need to add another message to bulletin create another instance |
| 51 | + with a different 'message_name' |
| 52 | + e.g. |
| 53 | + msg_1 = BulletinRestClient("message_1", "<some session key>") |
| 54 | + msg_2 = BulletinRestClient("message_2", "<some session key>") |
| 55 | +
|
| 56 | + Arguments: |
| 57 | + message_name: Name of the message in the Splunk's bulletin. |
| 58 | + session_key: Splunk access token. |
| 59 | + app: App name of namespace. |
| 60 | + context: Other configurations for Splunk rest client. |
| 61 | + """ |
| 62 | + |
| 63 | + self.message_name = message_name |
| 64 | + self.session_key = session_key |
| 65 | + self.app = app |
| 66 | + |
| 67 | + self._rest_client = rest_client.SplunkRestClient( |
| 68 | + self.session_key, app=self.app, **context |
| 69 | + ) |
| 70 | + |
| 71 | + def create_message( |
| 72 | + self, |
| 73 | + msg: str, |
| 74 | + severity: Severity = Severity.WARNING, |
| 75 | + capabilities: Optional[List[str]] = None, |
| 76 | + roles: Optional[List] = None, |
| 77 | + ): |
| 78 | + """Creates a message in the Splunk's bulletin. Calling this method |
| 79 | + multiple times for the same instance will overwrite existing message. |
| 80 | +
|
| 81 | + Arguments: |
| 82 | + msg: The message which will be displayed in the Splunk's bulletin |
| 83 | + severity: Severity level of the message. It has to be one of: 'info', 'warn', 'error'. |
| 84 | + If wrong severity is given, ValueError will be raised. |
| 85 | + capabilities: One or more capabilities that users must have to view the message. |
| 86 | + Capability names are validated. |
| 87 | + This argument should be provided as a list of string/s e.g. capabilities=['one', 'two']. |
| 88 | + If a non-existent capability is used, HTTP 400 BAD REQUEST exception will be raised. |
| 89 | + If argument is not a List[str] ValueError will be raised. |
| 90 | + roles: One or more roles that users must have to view the message. Role names are validated. |
| 91 | + This argument should be provided as a list of string/s e.g. roles=['user', 'admin']. |
| 92 | + If a non-existent role is used, HTTP 400 BAD REQUEST exception will be raised. |
| 93 | + If argument is not a List[str] ValueError will be raised. |
| 94 | + """ |
| 95 | + body = { |
| 96 | + "name": self.message_name, |
| 97 | + "value": msg, |
| 98 | + "severity": severity, |
| 99 | + "capability": [], |
| 100 | + "role": [], |
| 101 | + } |
| 102 | + |
| 103 | + if severity not in ( |
| 104 | + self.Severity.INFO, |
| 105 | + self.Severity.WARNING, |
| 106 | + self.Severity.ERROR, |
| 107 | + ): |
| 108 | + raise ValueError( |
| 109 | + "Severity must be one of (" |
| 110 | + "'BulletinRestClient.Severity.INFO', " |
| 111 | + "'BulletinRestClient.Severity.WARNING', " |
| 112 | + "'BulletinRestClient.Severity.ERROR'" |
| 113 | + ")." |
| 114 | + ) |
| 115 | + |
| 116 | + if capabilities: |
| 117 | + body["capability"] = self._validate_and_get_body_value( |
| 118 | + capabilities, "Capabilities must be a list of strings." |
| 119 | + ) |
| 120 | + |
| 121 | + if roles: |
| 122 | + body["role"] = self._validate_and_get_body_value( |
| 123 | + roles, "Roles must be a list of strings." |
| 124 | + ) |
| 125 | + |
| 126 | + self._rest_client.post(self.MESSAGES_ENDPOINT, body=body, headers=self.headers) |
| 127 | + |
| 128 | + def get_message(self): |
| 129 | + """Get specific message created by this instance.""" |
| 130 | + endpoint = f"{self.MESSAGES_ENDPOINT}/{self.message_name}" |
| 131 | + response = self._rest_client.get(endpoint, output_mode="json").body.read() |
| 132 | + return json.loads(response) |
| 133 | + |
| 134 | + def get_all_messages(self): |
| 135 | + """Get all messages in the bulletin.""" |
| 136 | + response = self._rest_client.get( |
| 137 | + self.MESSAGES_ENDPOINT, output_mode="json" |
| 138 | + ).body.read() |
| 139 | + return json.loads(response) |
| 140 | + |
| 141 | + def delete_message(self): |
| 142 | + """Delete specific message created by this instance.""" |
| 143 | + endpoint = f"{self.MESSAGES_ENDPOINT}/{self.message_name}" |
| 144 | + self._rest_client.delete(endpoint) |
| 145 | + |
| 146 | + @staticmethod |
| 147 | + def _validate_and_get_body_value(arg, error_msg) -> List: |
| 148 | + if type(arg) is list and (all(isinstance(el, str) for el in arg)): |
| 149 | + return [el for el in arg] |
| 150 | + else: |
| 151 | + raise ValueError(error_msg) |
0 commit comments