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.

283 lines
13 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# Copyright (C) 2005-2025 Splunk Inc. All Rights Reserved.
import uuid
from enum import Enum
import itsi_py3
from ITOA.itoa_object import ItoaObject, CRUDMethodTypes
from ITOA.setup_logging import logger
from ITOA.itoa_exceptions import ItoaError
class Status(int, Enum):
STATUS_UNKNOWN = 0
STATUS_EDIT = 1
STATUS_SAVE = 2
STATUS_SYNC = 3
STATUS_VALIDATE = 4
STATUS_PUBLISH = 5
STATUS_IMPORT = 6
STATUS_ADD_SERVICE = 7
STATUS_DELETE = 8
STATUS_PUBLISH_SUCCESS = 9
STATUS_PARTIAL_PUBLISH = 10
STATUS_PUBLISH_REVERT = 11
STATUS_PUBLISH_RESET = 12
@staticmethod
def pretty_string(status=STATUS_UNKNOWN):
"""
Retrun pretty string of the Status.
@type status: Status
@param status: status of the sandbox
"""
status_string = "Unknown"
if status == Status.STATUS_EDIT:
status_string = "Edit"
elif status == Status.STATUS_SAVE:
status_string = "Saving"
elif status == Status.STATUS_SYNC:
status_string = "Synchronizing"
elif status == Status.STATUS_PUBLISH:
status_string = "Publishing"
elif status == Status.STATUS_IMPORT:
status_string = "Importing"
elif status == Status.STATUS_ADD_SERVICE:
status_string = "Adding Sandbox Service"
elif status == Status.STATUS_DELETE:
status_string = "Deleting"
elif status == Status.STATUS_PUBLISH_SUCCESS:
status_string = "Published"
elif status == Status.STATUS_PARTIAL_PUBLISH:
status_string = "Partially published"
elif status == Status.STATUS_PUBLISH_REVERT:
status_string = "Reverting published services"
elif status == Status.STATUS_PUBLISH_RESET:
status_string = "Removing published services from sandbox"
return status_string
@staticmethod
def get_allowed_statuses(intermediate_status):
"""
Returns the list of allowed status for the given intermediate status
@param intermediate_status: Intermediate status of the sandbox
@type enum: Status enum
@rtype: list
@return: list of status that are allowed for the intermediate status
"""
allowed_statuses_by_intermediate_status = {Status.STATUS_DELETE: [Status.STATUS_EDIT, Status.STATUS_PUBLISH_SUCCESS, Status.STATUS_PARTIAL_PUBLISH], Status.STATUS_PUBLISH_REVERT: [
Status.STATUS_PUBLISH_SUCCESS, Status.STATUS_PARTIAL_PUBLISH], Status.STATUS_PUBLISH_RESET: [Status.STATUS_PUBLISH_SUCCESS, Status.STATUS_PARTIAL_PUBLISH]}
return allowed_statuses_by_intermediate_status.get(intermediate_status, [Status.STATUS_EDIT])
class ItsiSandbox(ItoaObject):
"""
Implements ITSI Sandbox object
"""
logger = logger
log_prefix = '[ITSI Sandbox] '
collection_name = 'itsi_sandbox'
object_type = 'sandbox'
MAX_SERVICE_SANDBOX_LIMIT = 20
@staticmethod
def get_allowed_statuses(intermediate_status):
"""
Returns the list of allowed status for the given intermediate status
@param intermediate_status: Intermediate status of the sandbox
@type enum: Status enum
@rtype: list
@return: list of status that are allowed for the intermediate status
"""
allowed_statuses_by_intermediate_status = {Status.STATUS_DELETE: [Status.STATUS_EDIT, Status.STATUS_PUBLISH_SUCCESS, Status.STATUS_PARTIAL_PUBLISH],
Status.STATUS_PUBLISH_REVERT: [Status.STATUS_PUBLISH_SUCCESS, Status.STATUS_PARTIAL_PUBLISH],
Status.STATUS_PUBLISH_RESET: [Status.STATUS_PUBLISH_SUCCESS, Status.STATUS_PARTIAL_PUBLISH],
Status.STATUS_SYNC: [Status.STATUS_EDIT, Status.STATUS_PUBLISH, Status.STATUS_PARTIAL_PUBLISH]
}
return allowed_statuses_by_intermediate_status.get(intermediate_status, [Status.STATUS_EDIT])
def __init__(self, session_key, current_user_name):
logger.info('Instantiating Sandbox Object')
self.session_key = session_key
self.current_user_name = current_user_name
super(ItsiSandbox, self).__init__(session_key,
current_user_name,
self.object_type,
collection_name=self.collection_name,
is_securable_object=False)
def create(self, owner, data, dupname_tag=None, transaction_id=None):
"""
Create object passed in. Extending the functionality to check for maximum allowed
Service Sandbox
@type owner: string
@param owner: user who is performing this operation
@type data: dictionary
@param data: object to create
@rtype: string
@return: id of object created if successful, throws exceptions on errors
"""
results = self.get_bulk(
owner, filter_data={}, fields=['_key'], transaction_id=transaction_id
)
if ( len(results) >= self.MAX_SERVICE_SANDBOX_LIMIT ):
raise ItoaError(
"The maximum number of service sandboxes has been reached.\
Delete an existing sandbox before creating a new one.",
logger, status_code=400)
return super(ItsiSandbox, self).create( owner, data, dupname_tag, transaction_id)
def do_additional_setup(self, owner, objects, req_source='unknown', method=CRUDMethodTypes.METHOD_UPSERT,
transaction_id=None, skip_local_failure=False):
"""
Override ItoaObject.do_additional_setup to add additional attributes to itsi_sandbox
before a write operation (create or update) is invoked on this object
@type owner: string
@param owner: user who is performing this operation
@type objects: list of dictionary
@param objects: list of objects being written
@type req_source: string
@param req_source: string identifying source of this request
@return: none, throws exceptions on errors
"""
if method == CRUDMethodTypes.METHOD_CREATE:
for json_data in objects:
json_data['creator'] = self.current_user_name
json_data['status'] = Status.STATUS_EDIT
json_data['count_of_services'] = 0
json_data['count_of_unique_service_templates'] = 0
def can_be_deleted(self, owner, objects, raise_error=False, transaction_id=None):
"""
Override ItoaObject.can_be_deleted to not delete the default sandbox
@type owner: string
@param owner: context of the request `nobody` vs an actual user
@type objects: list
@param objects: sandbox objects to check deletability on
@type raise_error: boolean
@param raise_error: if true, an error will be raised if no objects could be deleted
@rtype: list
@return: list - list of deletable objects
"""
results = []
objects = self._get_security_enforcer().enforce_security_on_delete(owner,
self, objects,
transaction_id=transaction_id)
for sandbox in objects:
if sandbox and sandbox.get('_key') != 'default_itsi_sandbox':
results.append(sandbox)
return results
def delete(self, owner, object_id, req_source='unknown', transaction_id=None):
"""
Overriding the Itoaobject Delete method
Delete object by id
@type owner: string
@param owner: user who is performing this operation
@type object_id: string
@param object_id: id of object to delete
@type req_source: string
@param req_source: identified source initiating the operation
@rtype: string
@return: id of object deleted on success, throws exceptions on errors
"""
from itsi.objects.itsi_sandbox_sync_log import (ItsiSandboxSyncLog, ACTION_TYPE_DELETE, STATUS_IN_PROGRESS, STATUS_SUCCESS,
STATUS_FAILED)
from itsi.sandbox.sandbox_utils import SandboxServiceUtils
stored_object = self.get(owner, object_id, req_source=req_source, transaction_id=transaction_id)
if not self.can_be_deleted(owner, [stored_object], raise_error=True, transaction_id=transaction_id):
return
sandbox_sync_log_interface = ItsiSandboxSyncLog(self.session_key, self.current_user_name)
if transaction_id is None:
transaction_id = uuid.uuid1().hex
allowed_statuses = Status.get_allowed_statuses(Status.STATUS_DELETE)
self.validate_and_return_current_sandbox_status(owner, object_id, req_source, transaction_id, allowed_statuses)
record = {
'action_type': ACTION_TYPE_DELETE,
'action_status': STATUS_IN_PROGRESS,
'errors': [],
}
sandbox_sync_log_interface.create_record(
owner, transaction_id, self.object_type, object_id, record,
)
results = []
try:
sand_utils = SandboxServiceUtils(self.session_key, self.current_user_name)
sand_utils.perform_sandbox_service_delete(owner, object_id, transaction_id)
sand_utils.clear_publish_references_from_sandbox(owner, object_id, transaction_id)
results = super(ItsiSandbox, self).delete(owner, object_id, req_source, transaction_id)
record_complete = {
'action_type': ACTION_TYPE_DELETE,
'action_status': STATUS_SUCCESS,
'errors': [],
}
except Exception as e:
record_complete = {
'action_type': ACTION_TYPE_DELETE,
'action_status': STATUS_FAILED,
'errors': [str(e)],
}
sandbox_sync_log_interface.update_record(owner, transaction_id, record_complete)
return results
def delete_bulk(
self,
owner,
filter_data=None,
req_source='unknown',
transaction_id=None
):
"""
Deletes objects matching criteria, if no filtering specified, deletes all objects of this object type
@type owner: string
@param owner: user who is performing this operation
@type filter_data: dictionary
@param filter_data: json filter constructed to filter data. Follows mongodb syntax
@type req_source: string
@param req_source: identified source initiating the operation
@return: none, throws exceptions on errors
"""
if transaction_id is None:
transaction_id = uuid.uuid1().hex
delete_objects = self.get_bulk(owner, filter_data=filter_data, fields=self.delete_object_fields,
transaction_id=transaction_id)
for delete_object in delete_objects:
sandbox_id = delete_object.get('_key')
# Special case for create record for delete operation
new_transaction_id = transaction_id + '-' + sandbox_id
self.delete(owner, sandbox_id, req_source, new_transaction_id)
def post_delete(self, owner, deleted_sandbox_ids, req_source='unknown',
method=CRUDMethodTypes.METHOD_UPSERT, transaction_id=None):
return
def validate_and_return_current_sandbox_status(self, owner, sandbox_id, req_source='Unknown', transaction_id=None, allowed_initial_statuses=[Status.STATUS_EDIT]):
"""
Validates if sandbox exists and if the status is Edit
@type string
@param sandbox_id: key of the sandbox
"""
sandbox_detail = self.get(owner, sandbox_id, req_source, transaction_id)
if not sandbox_detail:
logger.error(
"Unable to retrieve the sandbox details tid=%s sandbox_id=%s",
transaction_id,
sandbox_id)
self.raise_error_bad_validation(logger, 'Could not retrieve service sandbox details. Check that the inputted key is correct.', 409)
# This doesn't make sense, but the MR changes are piling up
elif sandbox_detail.get('status', Status.STATUS_EDIT) not in allowed_initial_statuses:
status_pretty = Status.pretty_string(sandbox_detail.get('status'))
logger.error(
("This action cant be completed because of a conflicting action. Refresh after a minute and try again. Current status is {}").format(status_pretty))
self.raise_error_bad_validation(
logger, 'This action cant be completed because of a conflicting action. Refresh after a minute and try again.', 409,
)
return sandbox_detail.get('status', Status.STATUS_EDIT)