You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
986 lines
43 KiB
986 lines
43 KiB
#!/usr/bin/env python
|
|
# coding=utf-8
|
|
|
|
__name__ = "trackme_rest_handler_maintenance.py"
|
|
__author__ = "TrackMe Limited"
|
|
__copyright__ = "Copyright 2022-2026, TrackMe Limited, U.K."
|
|
__credits__ = "TrackMe Limited, U.K."
|
|
__license__ = "TrackMe Limited, all rights reserved"
|
|
__version__ = "0.1.0"
|
|
__maintainer__ = "TrackMe Limited, U.K."
|
|
__email__ = "support@trackme-solutions.com"
|
|
__status__ = "PRODUCTION"
|
|
|
|
# Built-in libraries
|
|
import datetime
|
|
import json
|
|
import os
|
|
import sys
|
|
import time
|
|
|
|
import requests
|
|
|
|
# splunk home
|
|
splunkhome = os.environ["SPLUNK_HOME"]
|
|
|
|
# append current directory
|
|
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
# import libs
|
|
import import_declare_test
|
|
|
|
# set logging
|
|
from trackme_libs_logging import setup_logger
|
|
|
|
logger = setup_logger("trackme.rest.maintenance", "trackme_rest_api_maintenance.log")
|
|
# Redirect global logging to use the same handler
|
|
import logging
|
|
logging.getLogger().handlers = logger.handlers
|
|
logging.getLogger().setLevel(logger.level)
|
|
|
|
|
|
# import rest handler
|
|
import trackme_rest_handler
|
|
|
|
# import trackme libs
|
|
from trackme_libs import trackme_getloglevel, trackme_audit_event
|
|
|
|
# import Splunk libs
|
|
import splunklib.client as client
|
|
|
|
|
|
class TrackMeHandlerMaintenance_v2(trackme_rest_handler.RESTHandler):
|
|
def __init__(self, command_line, command_arg):
|
|
super(TrackMeHandlerMaintenance_v2, self).__init__(
|
|
command_line, command_arg, logger
|
|
)
|
|
|
|
def get_resource_group_desc_maintenance(self, request_info, **kwargs):
|
|
response = {
|
|
"resource_group_name": "maintenance",
|
|
"resource_group_desc": "The maintenance mode feature provides a built-in workflow to temporarily silence all alerts from TrackMe for a given period of time, which can be scheduled in advance.",
|
|
}
|
|
|
|
return {"payload": response, "status": 200}
|
|
|
|
# Check the global maintenance mode
|
|
def get_check_global_maintenance_status(self, request_info, **kwargs):
|
|
# declare
|
|
update_comment = "API update"
|
|
describe = False
|
|
|
|
# Retrieve from data
|
|
try:
|
|
resp_dict = json.loads(str(request_info.raw_args["payload"]))
|
|
except Exception as e:
|
|
resp_dict = None
|
|
|
|
if resp_dict is not None:
|
|
try:
|
|
describe = resp_dict["describe"]
|
|
if describe in ("true", "True"):
|
|
describe = True
|
|
except Exception as e:
|
|
describe = False
|
|
|
|
else:
|
|
# body is not required in this endpoint, if not submitted do not describe the usage
|
|
describe = False
|
|
|
|
if describe:
|
|
response = {
|
|
"describe": "This endpoint checks and returns the maintenance status. It requires a GET call with no data.",
|
|
"resource_desc": "Check and return the maintenance mode status",
|
|
"resource_spl_example": '| trackme mode=get url="/services/trackme/v2/maintenance/check_global_maintenance_status"',
|
|
}
|
|
|
|
return {"payload": response, "status": 200}
|
|
|
|
# Get service
|
|
service = client.connect(
|
|
owner="nobody",
|
|
app="trackme",
|
|
port=request_info.server_rest_port,
|
|
token=request_info.system_authtoken,
|
|
timeout=600,
|
|
)
|
|
|
|
# set loglevel
|
|
loglevel = trackme_getloglevel(
|
|
request_info.system_authtoken, request_info.server_rest_port
|
|
)
|
|
logger.setLevel(loglevel)
|
|
|
|
collection_name = "kv_trackme_maintenance_mode"
|
|
collection = service.kvstore[collection_name]
|
|
|
|
# get records
|
|
try:
|
|
records = collection.data.query()
|
|
key = records[0].get("_key")
|
|
except Exception as e:
|
|
key = None
|
|
|
|
# if we have no records yet, the maintenance mode is not setup yet, therefore it is disabled
|
|
if not key:
|
|
# Set the response record
|
|
response = {
|
|
"tenants_scope": "*",
|
|
"maintenance": False,
|
|
"maintenance_mode": "disabled",
|
|
"maintenance_message": "The global maintenance mode is currently disabled, all alerts from TrackMe are permitted",
|
|
"maintenance_comment": update_comment,
|
|
"epoch_updated": round(time.time(), 0),
|
|
"time_updated": time.strftime(
|
|
"%Y-%m-%d %H:%M", time.localtime(time.time())
|
|
),
|
|
"src_user": "nobody",
|
|
}
|
|
|
|
# insert a new record
|
|
try:
|
|
collection.data.insert(json.dumps(response))
|
|
except Exception as e:
|
|
logger.error(
|
|
f'failed to insert the maintenance record with exception="{str(e)}"'
|
|
)
|
|
|
|
# Render
|
|
return {"payload": response, "status": 200}
|
|
|
|
# if we have records, then investigate
|
|
else:
|
|
tenants_scope = records[0].get("tenants_scope", "*")
|
|
maintenance = records[0].get("maintenance")
|
|
maintenance_mode = records[0].get("maintenance_mode")
|
|
knowledge_record_id = records[0].get("knowledge_record_id", None)
|
|
|
|
# maintenance mode is enabled or scheduled, verify its expiration
|
|
if maintenance or maintenance_mode == "scheduled":
|
|
# get count down
|
|
maintenance_countdown = round(
|
|
int(records[0].get("maintenance_mode_end")) - time.time(), 0
|
|
)
|
|
|
|
# set bool
|
|
if not maintenance_countdown >= 0:
|
|
maintenance_mode_has_expired = True
|
|
else:
|
|
maintenance_mode_has_expired = False
|
|
|
|
# if count down has expired
|
|
if maintenance_mode_has_expired:
|
|
logger.info(
|
|
f'global maintenance mode was enabled and has now expired, count_down="{maintenance_countdown}"'
|
|
)
|
|
response = {
|
|
"tenants_scope": tenants_scope,
|
|
"maintenance": False,
|
|
"maintenance_mode": "disabled",
|
|
"maintenance_message": "The global maintenance mode has expired and was automatically disabled, all alerts from TrackMe are now permitted",
|
|
"maintenance_mode_start": records[0].get(
|
|
"maintenance_mode_start"
|
|
),
|
|
"maintenance_mode_end": records[0].get("maintenance_mode_end"),
|
|
"maintenance_comment": records[0].get("maintenance_comment"),
|
|
"epoch_updated": round(time.time(), 0),
|
|
"epoch_started": records[0].get("time_started"),
|
|
"time_updated": time.strftime(
|
|
"%Y-%m-%d %H:%M", time.localtime(time.time())
|
|
),
|
|
"time_started": time.strftime(
|
|
"%Y-%m-%d %H:%M",
|
|
time.localtime(int(records[0].get("epoch_started"))),
|
|
),
|
|
"src_user": records[0].get("src_user"),
|
|
"knowledge_record_id": knowledge_record_id,
|
|
}
|
|
|
|
# update record
|
|
try:
|
|
collection.data.update(str(key), json.dumps(response))
|
|
except Exception as e:
|
|
logger.error(
|
|
f'failed to updated the maintenance record with exception="{str(e)}"'
|
|
)
|
|
|
|
# Maintenance Knowledge DataBase
|
|
if knowledge_record_id:
|
|
data = {
|
|
"action": "stop_period",
|
|
"record_id": knowledge_record_id,
|
|
"update_comment": update_comment,
|
|
}
|
|
|
|
header = {
|
|
"Authorization": "Splunk %s" % request_info.session_key,
|
|
"Content-Type": "application/json",
|
|
}
|
|
|
|
try:
|
|
with requests.post(
|
|
f"{request_info.server_rest_uri}/services/trackme/v2/maintenance_kdb/admin/maintenance_kdb_manage_record",
|
|
data=json.dumps(data),
|
|
headers=header,
|
|
verify=False,
|
|
timeout=600,
|
|
) as response:
|
|
response.raise_for_status() # This will raise an HTTPError if the HTTP request returned an unsuccessful status code
|
|
response_json = response.json()
|
|
|
|
# Process successful response
|
|
logger.info(
|
|
f'Success stopping the period of the knowledge record, data="{json.dumps(response_json, indent=2)}"'
|
|
)
|
|
|
|
# Update the maintenance record with the new record_id
|
|
maintenance_record = collection.data.query_by_id(key)
|
|
# remove the knowledge_record_id from maintenance record
|
|
del maintenance_record["knowledge_record_id"]
|
|
# update
|
|
collection.data.update(
|
|
key, json.dumps(maintenance_record)
|
|
)
|
|
logger.info(
|
|
f'Success stopping the period of the maintenance record, data="{json.dumps(collection.data.query_by_id(key), indent=2)}"'
|
|
)
|
|
|
|
except requests.HTTPError as http_err:
|
|
# Handle HTTP errors
|
|
error_message = f'Failed to stop the knowledge record period, status_code={http_err.response.status_code}, response_text="{http_err.response.text}"'
|
|
logger.error(error_message)
|
|
return {
|
|
"payload": {"response": error_message},
|
|
"status": 500,
|
|
}
|
|
except Exception as e:
|
|
# Handle other exceptions
|
|
error_message = f'Failed to stop the knowledge record period, exception="{str(e)}"'
|
|
logger.error(error_message)
|
|
return {
|
|
"payload": {"response": error_message},
|
|
"status": 500,
|
|
}
|
|
|
|
# Render
|
|
return {"payload": response, "status": 200}
|
|
|
|
# and not expired yet
|
|
else:
|
|
# is this a scheduled maintenance?
|
|
|
|
if int(records[0].get("maintenance_mode_start")) > time.time():
|
|
logger.info(
|
|
f'global maintenance mode is scheduled, but not active yet, count_down="{maintenance_countdown}"'
|
|
)
|
|
response = {
|
|
"tenants_scope": tenants_scope,
|
|
"maintenance": False,
|
|
"maintenance_mode": "scheduled",
|
|
"maintenance_message": "The global maintenance mode is currently scheduled, alerts from TrackMe are currently permitted until it is activated",
|
|
"maintenance_mode_start": records[0].get(
|
|
"maintenance_mode_start"
|
|
),
|
|
"maintenance_mode_end": records[0].get(
|
|
"maintenance_mode_end"
|
|
),
|
|
"maintenance_countdown": round(
|
|
time.time()
|
|
- int(records[0].get("maintenance_mode_start"))
|
|
),
|
|
"maintenance_comment": records[0].get(
|
|
"maintenance_comment"
|
|
),
|
|
"epoch_updated": round(time.time(), 0),
|
|
"epoch_started": records[0].get("epoch_started"),
|
|
"time_updated": time.strftime(
|
|
"%Y-%m-%d %H:%M", time.localtime(time.time())
|
|
),
|
|
"time_started": time.strftime(
|
|
"%Y-%m-%d %H:%M",
|
|
time.localtime(int(records[0].get("epoch_started"))),
|
|
),
|
|
"src_user": records[0].get("src_user"),
|
|
"knowledge_record_id": knowledge_record_id,
|
|
}
|
|
|
|
else:
|
|
logger.info(
|
|
f'global maintenance mode is enabled and has not expired yet, count_down="{maintenance_countdown}"'
|
|
)
|
|
response = {
|
|
"tenants_scope": tenants_scope,
|
|
"maintenance": True,
|
|
"maintenance_mode": "enabled",
|
|
"maintenance_message": "The global maintenance mode is currently enabled, alerts from TrackMe are not permitted",
|
|
"maintenance_mode_start": records[0].get(
|
|
"maintenance_mode_start"
|
|
),
|
|
"maintenance_mode_end": records[0].get(
|
|
"maintenance_mode_end"
|
|
),
|
|
"maintenance_countdown": maintenance_countdown,
|
|
"maintenance_comment": records[0].get(
|
|
"maintenance_comment"
|
|
),
|
|
"epoch_updated": round(time.time(), 0),
|
|
"epoch_started": records[0].get("epoch_started"),
|
|
"time_updated": time.strftime(
|
|
"%Y-%m-%d %H:%M", time.localtime(time.time())
|
|
),
|
|
"time_started": time.strftime(
|
|
"%Y-%m-%d %H:%M",
|
|
time.localtime(int(records[0].get("epoch_started"))),
|
|
),
|
|
"src_user": records[0].get("src_user"),
|
|
"knowledge_record_id": knowledge_record_id,
|
|
}
|
|
|
|
# update record
|
|
try:
|
|
collection.data.update(str(key), json.dumps(response))
|
|
except Exception as e:
|
|
logger.error(
|
|
f'failed to updated the maintenance record with exception="{str(e)}"'
|
|
)
|
|
|
|
# Render
|
|
return {"payload": response, "status": 200}
|
|
|
|
# whatever - only enabled would be considered in the maintenance logic
|
|
else:
|
|
response = {
|
|
"tenants_scope": tenants_scope,
|
|
"maintenance": False,
|
|
"maintenance_mode": "disabled",
|
|
"maintenance_comment": update_comment,
|
|
"maintenance_message": "The maintenance is currently disabled, all alerts from TrackMe are permitted",
|
|
}
|
|
|
|
if not records[0].get("maintenance_mode") == "disabled":
|
|
# update record
|
|
try:
|
|
collection.data.update(str(key), json.dumps(response))
|
|
except Exception as e:
|
|
logger.error(
|
|
f'failed to updated the maintenance record with exception="{str(e)}"'
|
|
)
|
|
|
|
# Render
|
|
return {"payload": response, "status": 200}
|
|
|
|
# Enable the maintenance mode
|
|
def post_global_maintenance_enable(self, request_info, **kwargs):
|
|
# Declare
|
|
tenants_scope = ["*"]
|
|
maintenance_mode_start = None
|
|
maintenance_mode_end = None
|
|
maintenance_duration = None
|
|
add_knowledge_record = None
|
|
time_format = None
|
|
update_comment = None
|
|
|
|
describe = False
|
|
|
|
# Retrieve from data
|
|
try:
|
|
resp_dict = json.loads(str(request_info.raw_args["payload"]))
|
|
except Exception as e:
|
|
resp_dict = None
|
|
|
|
if resp_dict is not None:
|
|
try:
|
|
describe = resp_dict["describe"]
|
|
if describe in ("true", "True"):
|
|
describe = True
|
|
except Exception as e:
|
|
describe = False
|
|
if not describe:
|
|
try:
|
|
resp_dict = json.loads(str(request_info.raw_args["payload"]))
|
|
except Exception as e:
|
|
resp_dict = []
|
|
|
|
# Get tenants_scope, optional
|
|
try:
|
|
tenants_scope = resp_dict["tenants_scope"]
|
|
if tenants_scope == "*" or tenants_scope == "":
|
|
tenants_scope = ["*"]
|
|
except Exception as e:
|
|
tenants_scope = ["*"]
|
|
|
|
# if not already a list, convert to list from comma separated string
|
|
if not isinstance(tenants_scope, list):
|
|
tenants_scope = tenants_scope.split(",")
|
|
|
|
# if tenants_scope has more than entry, ensure to remove * if present
|
|
# also, tenants_scope cannot be empty, if empty add *
|
|
if len(tenants_scope) > 1:
|
|
try:
|
|
tenants_scope.remove("*")
|
|
except Exception as e:
|
|
pass
|
|
if len(tenants_scope) == 0:
|
|
tenants_scope = ["*"]
|
|
|
|
# Get start and end maintenance, both are optionals
|
|
|
|
# maintenance_mode_start
|
|
try:
|
|
maintenance_mode_start = resp_dict["maintenance_mode_start"]
|
|
except Exception as e:
|
|
maintenance_mode_start = 0
|
|
|
|
# maintenance_mode_end
|
|
try:
|
|
maintenance_mode_end = resp_dict["maintenance_mode_end"]
|
|
except Exception as e:
|
|
maintenance_mode_end = 0
|
|
|
|
# maintenance_duration
|
|
try:
|
|
maintenance_duration = int(resp_dict["maintenance_duration"])
|
|
except Exception as e:
|
|
maintenance_duration = 0
|
|
|
|
# time_format: optional and defaults to epochtime, alternative is date string in the format YYYY-MM-DDTHH:MM
|
|
try:
|
|
time_format = resp_dict["time_format"]
|
|
if time_format not in ("epochtime", "datestring"):
|
|
return {
|
|
"payload": {
|
|
"response": 'Invalid option for time_format="{}", valid options are: epochtime | datestring'
|
|
},
|
|
"status": 500,
|
|
}
|
|
except Exception as e:
|
|
time_format = "epochtime"
|
|
|
|
# Add a knowledge record
|
|
try:
|
|
add_knowledge_record = resp_dict["add_knowledge_record"]
|
|
if add_knowledge_record in ("true", "True"):
|
|
add_knowledge_record = True
|
|
else:
|
|
add_knowledge_record = False
|
|
except Exception as e:
|
|
add_knowledge_record = True
|
|
|
|
# Update comment is optional and used for audit changes
|
|
try:
|
|
update_comment = resp_dict["update_comment"]
|
|
except Exception as e:
|
|
update_comment = "API update"
|
|
|
|
else:
|
|
# body is not required in this endpoint
|
|
describe = False
|
|
|
|
if describe:
|
|
response = {
|
|
"describe": "This endpoint enables the maintenance mode, it requires a POST call with the following information:",
|
|
"resource_desc": "Enable global TrackMe maintenance mode",
|
|
"resource_spl_example": "| trackme mode=post url=\"/services/trackme/v2/maintenance/global_maintenance_enable\" body=\"{'maintenance_duration': '3600', 'update_comment': 'Enabling a TrackMe global maintenance for 1 hour of duration from now.'}\"",
|
|
"options": [
|
|
{
|
|
"tenants_scope": "OPTIONAL: the tenants scope for the maintenance expressed as a comma seperated list of values, defaults to * for all tenants",
|
|
"maintenance_duration": "(integer) OPTIONAL: the duration of the maintenance window in seconds, if unspecified and maintenance_mode_end is not specified either, defaults to now plus 24 hours",
|
|
"maintenance_mode_end": "OPTIONAL: the date time in epochtime format for the end of the maintenance window, it is overriden by maintenance_duration if specified, defaults to now plus 24 hours if not specified and maintenance_duration is not specified",
|
|
"maintenance_mode_start": "OPTIONAL: the date time in epochtime format for the start of the maintennce window, defaults to now if not specified",
|
|
"time_format": "OPTIONAL: the time format when submitting start and end maintenance values, defaults to epochtime and can alternatively be set to datestring which expects YYYY-MM-DDTHH:MM as the input format",
|
|
"add_knowledge_record": "OPTIONAL: a boolean value to indicate if a knowledge record should be added to the knowledge database, defaults to true",
|
|
"update_comment": "OPTIONAL: a comment for the update, comments are added to the audit record, if unset will be defined to: API update",
|
|
}
|
|
],
|
|
}
|
|
|
|
return {"payload": response, "status": 200}
|
|
|
|
# Calculates start and end
|
|
time_updated = round(time.time())
|
|
|
|
if time_format == "datestring":
|
|
# convert maintenance start
|
|
try:
|
|
maintenance_mode_start_dt = datetime.datetime.strptime(
|
|
str(maintenance_mode_start), "%Y-%m-%dT%H:%M"
|
|
)
|
|
# Make the datetime timezone-aware as UTC
|
|
maintenance_mode_start_dt = maintenance_mode_start_dt.replace(
|
|
tzinfo=datetime.timezone.utc
|
|
)
|
|
maintenance_mode_start = int(
|
|
round(float(maintenance_mode_start_dt.timestamp()))
|
|
)
|
|
except Exception as e:
|
|
return {
|
|
"payload": {
|
|
"response": f'Exception while trying to convert the input datestring, exception="{str(e)}"'
|
|
},
|
|
"status": 500,
|
|
}
|
|
|
|
# convert maintenance end
|
|
try:
|
|
maintenance_mode_end_dt = datetime.datetime.strptime(
|
|
str(maintenance_mode_end), "%Y-%m-%dT%H:%M"
|
|
)
|
|
# Make the datetime timezone-aware as UTC
|
|
maintenance_mode_end_dt = maintenance_mode_end_dt.replace(
|
|
tzinfo=datetime.timezone.utc
|
|
)
|
|
maintenance_mode_end = int(
|
|
round(float(maintenance_mode_end_dt.timestamp()))
|
|
)
|
|
except Exception as e:
|
|
return {
|
|
"payload": {
|
|
"response": f'Exception while trying to convert the input datestring, exception="{str(e)}"'
|
|
},
|
|
"status": 500,
|
|
}
|
|
|
|
else:
|
|
# if maintenance start is not specified, starts at now
|
|
if (maintenance_mode_start is None) or (maintenance_mode_start <= 0):
|
|
maintenance_mode_start = str(round(time_updated))
|
|
|
|
# if maintenance end is not specified, and maintenance duration is not specified either, defaults to now + 24 hours
|
|
if (maintenance_mode_end is None) or (maintenance_mode_end <= 0):
|
|
maintenance_mode_end = str(round(time.time() + 86400))
|
|
|
|
# if maintenance duration is specified, it overrides the maintenance end whenever it is specified or not
|
|
if (maintenance_duration is not None) and (maintenance_duration > 0):
|
|
maintenance_mode_end = str(round(time.time() + maintenance_duration))
|
|
|
|
# control: the start of the maintenance cannot be in the past (allow 5 min margin)
|
|
if int(maintenance_mode_start) - time.time() < -300:
|
|
return {
|
|
"payload": {
|
|
"response": f"The maintenance start cannot be in the past, it is in past of {round(int(maintenance_mode_start) - time.time())} seconds"
|
|
},
|
|
"status": 500,
|
|
}
|
|
|
|
# Get service
|
|
service = client.connect(
|
|
owner="nobody",
|
|
app="trackme",
|
|
port=request_info.server_rest_port,
|
|
token=request_info.system_authtoken,
|
|
timeout=600,
|
|
)
|
|
|
|
# get current user
|
|
username = request_info.user
|
|
|
|
# set loglevel
|
|
loglevel = trackme_getloglevel(
|
|
request_info.system_authtoken, request_info.server_rest_port
|
|
)
|
|
logger.setLevel(loglevel)
|
|
|
|
# Set the collection
|
|
collection_name = "kv_trackme_maintenance_mode"
|
|
collection = service.kvstore[collection_name]
|
|
|
|
# Get the current record
|
|
# Notes: the record is returned as an array, as we search for a specific record, we expect one record only
|
|
|
|
try:
|
|
records = collection.data.query()
|
|
key = records[0].get("_key")
|
|
|
|
except Exception as e:
|
|
key = None
|
|
|
|
# set count down
|
|
maintenance_countdown = round(int(maintenance_mode_end) - time.time(), 0)
|
|
|
|
# set maintenance_mode_start and maintenance_mode_end to int
|
|
try:
|
|
maintenance_mode_start = int(round(float(maintenance_mode_start), 0))
|
|
except:
|
|
pass
|
|
try:
|
|
maintenance_mode_end = int(round(float(maintenance_mode_end), 0))
|
|
except:
|
|
pass
|
|
|
|
# is this a scheduled maintenance?
|
|
if int(maintenance_mode_start) > time.time():
|
|
logger.info(
|
|
f'global maintenance mode is scheduled, but not active yet, count_down="{maintenance_countdown}"'
|
|
)
|
|
maintenance_mode_response = {
|
|
"tenants_scope": tenants_scope,
|
|
"maintenance": False,
|
|
"maintenance_mode": "scheduled",
|
|
"maintenance_message": "The global maintenance mode is currently scheduled, alerts from TrackMe are currently permitted until it is activated",
|
|
"maintenance_mode_start": maintenance_mode_start,
|
|
"maintenance_mode_end": maintenance_mode_end,
|
|
"maintenance_countdown": maintenance_countdown,
|
|
"maintenance_comment": update_comment,
|
|
"epoch_updated": round(time.time(), 0),
|
|
"time_updated": time.strftime(
|
|
"%Y-%m-%d %H:%M", time.localtime(time.time())
|
|
),
|
|
"epoch_started": round(time.time(), 0),
|
|
"time_started": time.strftime(
|
|
"%Y-%m-%d %H:%M", time.localtime(time.time())
|
|
),
|
|
"src_user": records[0].get("src_user"),
|
|
}
|
|
|
|
else:
|
|
# Set response
|
|
maintenance_mode_response = {
|
|
"tenants_scope": tenants_scope,
|
|
"maintenance": True,
|
|
"maintenance_mode": "enabled",
|
|
"maintenance_message": "The global maintenance mode is currently enabled, alerts from TrackMe are not permitted",
|
|
"maintenance_mode_start": maintenance_mode_start,
|
|
"maintenance_mode_end": maintenance_mode_end,
|
|
"maintenance_countdown": maintenance_countdown,
|
|
"maintenance_comment": update_comment,
|
|
"epoch_updated": round(time.time(), 0),
|
|
"time_updated": time.strftime(
|
|
"%Y-%m-%d %H:%M", time.localtime(time.time())
|
|
),
|
|
"epoch_started": round(time.time(), 0),
|
|
"time_started": time.strftime(
|
|
"%Y-%m-%d %H:%M", time.localtime(time.time())
|
|
),
|
|
"src_user": username,
|
|
}
|
|
|
|
if key:
|
|
# Update the record
|
|
collection.data.update(str(key), json.dumps(maintenance_mode_response))
|
|
else:
|
|
# Insert the record
|
|
kv_response = collection.data.insert(json.dumps(maintenance_mode_response))
|
|
key = kv_response.get("_key")
|
|
|
|
# record an audit change
|
|
trackme_audit_event(
|
|
request_info.system_authtoken,
|
|
request_info.server_rest_uri,
|
|
"all",
|
|
request_info.user,
|
|
"success",
|
|
"enable maintenance mode",
|
|
"all",
|
|
"all",
|
|
str(json.dumps(collection.data.query_by_id(key), indent=1)),
|
|
f'The maintenance mode was enabled successfully by user="{username}"',
|
|
str(update_comment),
|
|
)
|
|
|
|
# log
|
|
logger.info(
|
|
f'TrackMe maintenance mode was enabled, record="{json.dumps(maintenance_mode_response, indent=2)}"'
|
|
)
|
|
|
|
# add knowledge record
|
|
if add_knowledge_record:
|
|
# set data
|
|
"""
|
|
"tenants_scope": "OPTIONAL: the tenants scope for the maintenance expressed as a comma seperated list of values, defaults to * for all tenants",
|
|
"time_start": "MANDATORY: the start time of the maintenance, it can be specified in epochtime or datestring format",
|
|
"time_end": "MANDATORY: the end time of the maintenance, it can be specified in epochtime or datestring format",
|
|
"no_days_validity": "OPTIONAL: the number of days of validity of the maintenance record, if set to 0, the maintenance record is valid forever",
|
|
"reason": "MANDATORY: the reason for the maintenance",
|
|
"type": "MANDATORY: the type of the maintenance, it can be either planned or unplanned",
|
|
"add_info": "OPTIONAL: additional information about the maintenance",
|
|
"no_days_validity": "OPTIONAL: the number of days of validity of the maintenance record, if set to 0, the maintenance record is valid forever",
|
|
"time_format": "OPTIONAL: the time format when submitting start and end maintenance values, defaults to epochtime and can alternatively be set to datestring which expects YYYY-MM-DDTHH:MM as the input format",
|
|
"update_comment": "OPTIONAL: a comment for the update, comments are added to the audit record, if unset will be defined to: API update",
|
|
"""
|
|
|
|
data = {
|
|
"tenants_scope": ",".join(tenants_scope),
|
|
"time_start": maintenance_mode_start,
|
|
"time_end": maintenance_mode_end,
|
|
"no_days_validity": 0,
|
|
"reason": "TrackMe global maintenance",
|
|
"type": "planned",
|
|
"add_info": update_comment,
|
|
"update_comment": update_comment,
|
|
}
|
|
|
|
header = {
|
|
"Authorization": "Splunk %s" % request_info.session_key,
|
|
"Content-Type": "application/json",
|
|
}
|
|
|
|
try:
|
|
with requests.post(
|
|
f"{request_info.server_rest_uri}/services/trackme/v2/maintenance_kdb/admin/maintenance_kdb_add_record",
|
|
data=json.dumps(data),
|
|
headers=header,
|
|
verify=False,
|
|
timeout=600,
|
|
) as response:
|
|
response.raise_for_status() # This will raise an HTTPError if the HTTP request returned an unsuccessful status code
|
|
response_json = response.json()
|
|
|
|
# Process successful response
|
|
record_id = response_json.get("record_id")
|
|
logger.info(
|
|
f'Success creating the knowledge record, data="{json.dumps(response_json, indent=2)}"'
|
|
)
|
|
|
|
# Update the maintenance record with the new record_id
|
|
maintenance_record = collection.data.query_by_id(key)
|
|
maintenance_record["knowledge_record_id"] = record_id
|
|
maintenance_mode_response["knowledge_record_id"] = record_id
|
|
collection.data.update(key, json.dumps(maintenance_record))
|
|
logger.info(
|
|
f'Success updating the maintenance record, data="{json.dumps(collection.data.query_by_id(key), indent=2)}"'
|
|
)
|
|
|
|
except requests.HTTPError as http_err:
|
|
# Handle HTTP errors
|
|
error_message = f'Failed to create the knowledge record, status_code={http_err.response.status_code}, response_text="{http_err.response.text}"'
|
|
logger.error(error_message)
|
|
return {
|
|
"payload": {"response": error_message},
|
|
"status": 500,
|
|
}
|
|
except Exception as e:
|
|
# Handle other exceptions
|
|
error_message = (
|
|
f'Failed to create the knowledge record, exception="{str(e)}"'
|
|
)
|
|
logger.error(error_message)
|
|
return {
|
|
"payload": {"response": error_message},
|
|
"status": 500,
|
|
}
|
|
|
|
# render resoonse
|
|
return {"payload": maintenance_mode_response, "status": 200}
|
|
|
|
# Disable the maintenance mode
|
|
def post_maintenance_disable(self, request_info, **kwargs):
|
|
# Declare
|
|
update_comment = None
|
|
describe = False
|
|
|
|
# Retrieve from data
|
|
try:
|
|
resp_dict = json.loads(str(request_info.raw_args["payload"]))
|
|
except Exception as e:
|
|
resp_dict = None
|
|
|
|
if resp_dict is not None:
|
|
try:
|
|
describe = resp_dict["describe"]
|
|
if describe in ("true", "True"):
|
|
describe = True
|
|
except Exception as e:
|
|
describe = False
|
|
if not describe:
|
|
try:
|
|
resp_dict = json.loads(str(request_info.raw_args["payload"]))
|
|
except Exception as e:
|
|
resp_dict = []
|
|
|
|
# Update comment is optional and used for audit changes
|
|
try:
|
|
update_comment = resp_dict["update_comment"]
|
|
except Exception as e:
|
|
update_comment = "API update"
|
|
|
|
else:
|
|
# body is not required in this endpoint
|
|
describe = False
|
|
|
|
if describe:
|
|
response = {
|
|
"describe": "This endpoint disables the maintenance mode, it requires a POST call with the following information:",
|
|
"resource_desc": "Disable global TrackMe maintenance mode",
|
|
"resource_spl_example": "| trackme mode=post url=\"/services/trackme/v2/maintenance/maintenance_disable\" body=\"{'update_comment': 'All operations done, disabling global TrackMe maintenance mode.'}\"",
|
|
"options": [
|
|
{
|
|
"update_comment": "OPTIONAL: a comment for the update, comments are added to the audit record, if unset will be defined to: API update",
|
|
}
|
|
],
|
|
}
|
|
|
|
return {"payload": response, "status": 200}
|
|
|
|
# Get service
|
|
service = client.connect(
|
|
owner="nobody",
|
|
app="trackme",
|
|
port=request_info.server_rest_port,
|
|
token=request_info.system_authtoken,
|
|
timeout=600,
|
|
)
|
|
|
|
# get current user
|
|
username = request_info.user
|
|
|
|
# set loglevel
|
|
loglevel = trackme_getloglevel(
|
|
request_info.system_authtoken, request_info.server_rest_port
|
|
)
|
|
logger.setLevel(loglevel)
|
|
|
|
# Set the collection
|
|
collection_name = "kv_trackme_maintenance_mode"
|
|
collection = service.kvstore[collection_name]
|
|
|
|
# Get the current record
|
|
# Notes: the record is returned as an array, as we search for a specific record, we expect one record only
|
|
|
|
try:
|
|
kvrecord = collection.data.query()[0]
|
|
key = kvrecord.get("_key")
|
|
|
|
except Exception as e:
|
|
key = None
|
|
|
|
# Set the response record
|
|
response_maintenance_mode = {
|
|
"tenants_scope": "*", # disable maintenance for all tenants
|
|
"maintenance": False,
|
|
"maintenance_mode": "disabled",
|
|
"maintenance_message": "The global maintenance mode is currently disabled, all alerts from TrackMe are permitted",
|
|
"maintenance_comment": update_comment,
|
|
"epoch_updated": round(time.time(), 0),
|
|
"time_updated": time.strftime(
|
|
"%Y-%m-%d %H:%M", time.localtime(time.time())
|
|
),
|
|
"src_user": username,
|
|
}
|
|
|
|
# Process
|
|
if key:
|
|
# check the current status
|
|
maintenance = kvrecord.get("maintenance")
|
|
maintenance_mode = kvrecord.get("maintenance_mode")
|
|
# maintenance knowledge database, check if we have a knowledge_record_id, if we do, call the endpoint with the stop_period
|
|
knowledge_record_id = kvrecord.get("knowledge_record_id", None)
|
|
|
|
# if maintenance is currently enabled, an update is required
|
|
if maintenance or maintenance_mode == "scheduled":
|
|
# Update the record
|
|
collection.data.update(str(key), json.dumps(response_maintenance_mode))
|
|
|
|
# record an audit change
|
|
trackme_audit_event(
|
|
request_info.system_authtoken,
|
|
request_info.server_rest_uri,
|
|
"all",
|
|
request_info.user,
|
|
"success",
|
|
"disable maintenance mode",
|
|
"all",
|
|
"all",
|
|
str(json.dumps(collection.data.query_by_id(key), indent=1)),
|
|
f'The maintenance mode was disabled successfully by user="{username}"',
|
|
str(update_comment),
|
|
)
|
|
|
|
# log
|
|
logger.info(
|
|
f'TrackMe maintenance mode was disabled, record="{json.dumps(response_maintenance_mode, indent=2)}"'
|
|
)
|
|
|
|
# Maintenance Knowledge DataBase
|
|
if knowledge_record_id:
|
|
data = {
|
|
"action": "stop_period",
|
|
"record_id": knowledge_record_id,
|
|
"update_comment": update_comment,
|
|
}
|
|
|
|
header = {
|
|
"Authorization": "Splunk %s" % request_info.session_key,
|
|
"Content-Type": "application/json",
|
|
}
|
|
|
|
try:
|
|
with requests.post(
|
|
f"{request_info.server_rest_uri}/services/trackme/v2/maintenance_kdb/admin/maintenance_kdb_manage_record",
|
|
data=json.dumps(data),
|
|
headers=header,
|
|
verify=False,
|
|
timeout=600,
|
|
) as response:
|
|
response.raise_for_status() # This will raise an HTTPError if the HTTP request returned an unsuccessful status code
|
|
response_json = response.json()
|
|
|
|
# Process successful response
|
|
logger.info(
|
|
f'Success stopping the period of the knowledge record, data="{json.dumps(response_json, indent=2)}"'
|
|
)
|
|
|
|
except requests.HTTPError as http_err:
|
|
# Handle HTTP errors
|
|
error_message = f'Failed to stop the knowledge record period, status_code={http_err.response.status_code}, response_text="{http_err.response.text}"'
|
|
logger.error(error_message)
|
|
return {
|
|
"payload": {"response": error_message},
|
|
"status": 500,
|
|
}
|
|
except Exception as e:
|
|
# Handle other exceptions
|
|
error_message = f'Failed to stop the knowledge record period, exception="{str(e)}"'
|
|
logger.error(error_message)
|
|
return {
|
|
"payload": {"response": error_message},
|
|
"status": 500,
|
|
}
|
|
|
|
else:
|
|
# we haven't done any change, override the response
|
|
response = {
|
|
"tenants_scope": "*",
|
|
"maintenance": False,
|
|
"maintenance_mode": kvrecord.get("maintenance_mode"),
|
|
"maintenance_message": kvrecord.get("maintenance_message"),
|
|
"maintenance_comment": kvrecord.get("maintenance_comment"),
|
|
"epoch_updated": kvrecord.get("epoch_updated"),
|
|
"time_updated": kvrecord.get("time_updated"),
|
|
"src_user": kvrecord.get("src_user"),
|
|
}
|
|
|
|
# log
|
|
logger.info(
|
|
f'TrackMe maintenance mode disablement was requested, however it is already disabled, record="{json.dumps(response, indent=2)}"'
|
|
)
|
|
|
|
else:
|
|
# Insert the record
|
|
collection.data.insert(json.dumps(response))
|
|
|
|
# record an audit change
|
|
trackme_audit_event(
|
|
request_info.system_authtoken,
|
|
request_info.server_rest_uri,
|
|
"all",
|
|
request_info.user,
|
|
"success",
|
|
"disable maintenance mode",
|
|
"all",
|
|
"all",
|
|
str(json.dumps(collection.data.query_by_id(key), indent=1)),
|
|
f'The maintenance mode was disabled successfully by user="{username}"',
|
|
str(update_comment),
|
|
)
|
|
|
|
# log
|
|
logger.info(
|
|
f'TrackMe maintenance mode was disabled, record="{json.dumps(response, indent=2)}"'
|
|
)
|
|
|
|
# render response
|
|
return {
|
|
"payload": collection.data.query()[0],
|
|
"status": 200,
|
|
}
|