-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Syakima/CIAC-15740/forcepoint-add-refresh-command #43628
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 9 commits
7fb2d0b
5906a79
b2d5c48
6e0aa68
91b8786
39e1bf3
c03754f
58b6b0e
084b359
54c1d9a
c2a413a
cae8dbd
8ecc2de
4823b12
fda412d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,12 +3,13 @@ | |
| from CommonServerPython import * # noqa # pylint: disable=unused-wildcard-import | ||
| from CommonServerUserPython import * # noqa | ||
| from smc import session | ||
| from smc.administration.tasks import Task | ||
| from smc.elements.network import IPList, DomainName, Host | ||
| from smc.core.engine import Engine | ||
| from smc.base.model import Element | ||
| from smc.policy.layer3 import FirewallTemplatePolicy, FirewallPolicy | ||
| from smc.policy.rule import IPv6Rule, Rule | ||
| from smc.api.exceptions import ElementNotFound | ||
| from smc.api.exceptions import ElementNotFound, TaskRunFailed | ||
| import urllib3 | ||
| from typing import Any | ||
|
|
||
|
|
@@ -20,6 +21,9 @@ | |
| DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ" # ISO8601 format with UTC, default in XSOAR | ||
| DEFAULT_LIMIT = 50 | ||
| API_VERSION = "6.10" | ||
|
|
||
| DEFAULT_POLLING_INTERVAL_IN_SECONDS = 30 | ||
| DEFAULT_POLLING_TIMEOUT_IN_SECONDS = 600 | ||
| """ CLIENT CLASS """ | ||
|
|
||
|
|
||
|
|
@@ -819,6 +823,86 @@ def list_engine_command(args: dict[str, Any]) -> CommandResults: | |
| ) | ||
|
|
||
|
|
||
| @polling_function( | ||
| "forcepoint-smc-engine-refresh", | ||
| poll_message="Refreshing engines:", | ||
| requires_polling_arg=False, | ||
| interval=arg_to_number(demisto.args().get("interval_in_seconds")) or DEFAULT_POLLING_INTERVAL_IN_SECONDS, | ||
| timeout=arg_to_number(demisto.args().get("timeout_in_seconds")) or DEFAULT_POLLING_TIMEOUT_IN_SECONDS, | ||
| ) | ||
| def engine_refresh_command(args: dict) -> PollResult: | ||
| """ | ||
| Refresh the specified engines. | ||
|
|
||
| This command initiates refresh tasks and polls for status until complete. | ||
|
|
||
| Args: | ||
| args: Command arguments including engine names. | ||
|
|
||
| Returns: | ||
| PollResult: Contains the refresh status or indicates to continue polling. | ||
| """ | ||
| task_ids = argToList(args.get("task_ids", [])) | ||
| engine_names = argToList(args.get("engine_name", [])) | ||
|
|
||
| if not task_ids: | ||
| # first call - initiate refresh tasks | ||
| failed_engines = [] | ||
|
|
||
| demisto.debug("Creating refresh tasks:") | ||
| for engine_name in engine_names: | ||
| try: | ||
| engine = Engine(engine_name) | ||
| poller = engine.refresh() | ||
| task_ids.append(poller.task.href) | ||
| demisto.debug(f"\t- (Engine: {engine_name}, Task: {poller.task.href})") | ||
| except TaskRunFailed: | ||
| failed_engines.append(engine_name) | ||
|
|
||
| if failed_engines: | ||
| raise DemistoException( | ||
| f"Failed to initiate refresh tasks for engines ({', '.join(failed_engines)}), refresh policy is locked." | ||
| ) | ||
|
|
||
| demisto.debug("Completed task initiation.") | ||
| demisto.debug(f"Initiated refresh tasks for engines {', '.join(engine_names)}.") | ||
| demisto.debug(f"Tasks IDs: {task_ids}") | ||
| args["task_ids"] = task_ids | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Store
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @TheL0L I would also recommend doing this (also ensures the order is kept and not changed).
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @kamalq97 Can we guarantee that the ids will not have commas within them, and if they do - escape them? |
||
|
|
||
| # poll for refresh status | ||
| completed_tasks = [] | ||
| pending_tasks = [] | ||
|
|
||
| demisto.debug("Restoring refresh tasks, assignments are expected to be the same as during creation:") | ||
| for task_id, engine_name in zip(task_ids, engine_names): | ||
| task = Task({"follower": task_id}) | ||
| demisto.debug(f"\t- (Engine: {engine_name}, Task: {task_id})") | ||
| task = task.update_status() | ||
| if not task.in_progress: | ||
|
TheL0L marked this conversation as resolved.
|
||
| completed_tasks.append(engine_name) | ||
| else: | ||
| pending_tasks.append((task_id, engine_name)) | ||
|
|
||
| demisto.debug(f"Refresh status:\n{pending_tasks=}\n{completed_tasks=}") | ||
|
|
||
| if not pending_tasks: | ||
| demisto.debug("All refresh tasks are complete.") | ||
| return PollResult( | ||
| response=CommandResults( | ||
|
TheL0L marked this conversation as resolved.
|
||
| readable_output=f"Existing policy on specified devices ({', '.join(engine_names)}) were refreshed successfully." | ||
| ), | ||
| continue_to_poll=False, | ||
| ) | ||
|
|
||
| demisto.info(f"Refresh tasks {pending_tasks} are still in progress, continuing to poll...") | ||
|
|
||
| return PollResult( | ||
| response=CommandResults(readable_output="Refresh tasks are still in progress, continuing to poll..."), | ||
| continue_to_poll=True, | ||
| args_for_next_run=args, | ||
| ) | ||
|
|
||
|
|
||
| """ MAIN FUNCTION """ | ||
|
|
||
|
|
||
|
|
@@ -881,6 +965,8 @@ def main(): | |
| return_results(delete_rule_command(demisto.args())) | ||
| elif command == "forcepoint-smc-engine-list": | ||
| return_results(list_engine_command(demisto.args())) | ||
| elif command == "forcepoint-smc-engine-refresh": | ||
| return_results(engine_refresh_command(demisto.args())) | ||
| # Log exceptions and return errors | ||
| except Exception as e: | ||
| return_error(f"Failed to execute {command} command.\nError:\n{str(e)}") | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,13 +20,14 @@ | |
| list_rule_command, | ||
| delete_rule_command, | ||
| create_rule_command, | ||
| engine_refresh_command, | ||
| IPList, | ||
| Host, | ||
| DomainName, | ||
| FirewallPolicy, | ||
| DemistoException, | ||
| ) | ||
| from smc.api.exceptions import ElementNotFound | ||
| from smc.api.exceptions import ElementNotFound, TaskRunFailed | ||
| from smc.base.collection import CollectionManager | ||
|
|
||
|
|
||
|
|
@@ -112,15 +113,32 @@ def save(self): | |
| return | ||
|
|
||
|
|
||
| class mock_Task: | ||
| def __init__(self, href: str, in_progress: bool = True): | ||
| self.href = href | ||
| self.in_progress = in_progress | ||
|
|
||
| def update_status(self): | ||
| return self | ||
|
|
||
|
|
||
| class mock_Poller: | ||
| def __init__(self, task: mock_Task): | ||
| self.task = task | ||
|
|
||
|
|
||
| class mock_Engine: | ||
| def __init__(self, name: str, comment: str): | ||
| def __init__(self, name: str, comment: str = ""): | ||
| self.name = name | ||
| self.comment = comment | ||
| self.pending_changes = mock_Changes() | ||
|
|
||
| def objects(self): | ||
| return mock_GenericRuleEntity() | ||
|
|
||
| def refresh(self): | ||
| return mock_Poller(mock_Task(href=self.name)) | ||
|
|
||
|
|
||
| class mock_Changes: | ||
| def __init__(self): | ||
|
|
@@ -636,3 +654,89 @@ def test_update_rule_command(mocker): | |
| response = update_rule_command(args) | ||
|
|
||
| assert "The rule name to the policy name was updated successfully." in response.readable_output | ||
|
|
||
|
|
||
| def test_engine_refresh_command_initiation(mocker): | ||
| """ | ||
| Given: | ||
| - engine_name argument | ||
| When: | ||
| - Calling engine_refresh_command for the first time (no task_ids) | ||
| Then: | ||
| - Ensure refresh is initiated, task_ids are returned in args_for_next_run, and continue_to_poll is True | ||
| """ | ||
| args = {"engine_name": ["engine1", "engine2"]} | ||
| mocker.patch("ForcepointSecurityManagementCenter.Engine", side_effect=lambda name: mock_Engine(name)) | ||
| mocker.patch("ForcepointSecurityManagementCenter.Task", side_effect=lambda d: mock_Task(d.get("follower"))) | ||
|
|
||
| result = engine_refresh_command(args) | ||
|
|
||
| assert result.scheduled_command is not None | ||
| assert result.scheduled_command._command is not None | ||
| assert result.scheduled_command._command == "forcepoint-smc-engine-refresh" | ||
| assert result.scheduled_command._args is not None | ||
| assert "task_ids" in result.scheduled_command._args | ||
| assert result.scheduled_command._args["task_ids"] == ["engine1", "engine2"] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update test to expect a comma-separated string. |
||
|
|
||
|
|
||
| def test_engine_refresh_command_polling_in_progress(mocker): | ||
| """ | ||
| Given: | ||
| - task_ids and engine_name arguments | ||
| When: | ||
| - Calling engine_refresh_command while tasks are still in progress | ||
| Then: | ||
| - Ensure continue_to_poll is True | ||
| """ | ||
| args = {"engine_name": ["engine1"], "task_ids": ["engine1"]} | ||
| mocker.patch("ForcepointSecurityManagementCenter.Task", side_effect=lambda d: mock_Task(d.get("follower"), in_progress=True)) | ||
|
|
||
| result = engine_refresh_command(args) | ||
|
|
||
| assert result.scheduled_command is not None | ||
| assert result.scheduled_command._command is not None | ||
| assert result.scheduled_command._command == "forcepoint-smc-engine-refresh" | ||
| assert result.scheduled_command._args is not None | ||
| assert "task_ids" in result.scheduled_command._args | ||
| assert result.scheduled_command._args["task_ids"] == ["engine1"] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update test to expect a comma-separated string. |
||
|
|
||
|
|
||
| def test_engine_refresh_command_polling_complete(mocker): | ||
| """ | ||
| Given: | ||
| - task_ids and engine_name arguments | ||
| When: | ||
| - Calling engine_refresh_command when all tasks are finished | ||
| Then: | ||
| - Ensure continue_to_poll is False and success message is returned | ||
| """ | ||
| args = {"engine_name": ["engine1", "engine2"], "task_ids": ["engine1", "engine2"]} | ||
| mocker.patch("ForcepointSecurityManagementCenter.Task", side_effect=lambda d: mock_Task(d.get("follower"), in_progress=False)) | ||
|
|
||
| result = engine_refresh_command(args) | ||
|
|
||
| assert result.scheduled_command is None | ||
| assert "refreshed successfully" in result.readable_output | ||
|
|
||
|
|
||
| def test_engine_refresh_command_failure(mocker): | ||
| """ | ||
| Given: | ||
| - engine_name argument | ||
| When: | ||
| - engine.refresh() raises TaskRunFailed | ||
| Then: | ||
| - Ensure DemistoException is raised with the correct engine names | ||
| """ | ||
| args = {"engine_name": ["engine1", "engine2"]} | ||
|
|
||
| def mock_engine_side_effect(name): | ||
| engine = mock_Engine(name) | ||
| if name == "engine2": | ||
| mocker.patch.object(engine, "refresh", side_effect=TaskRunFailed("Locked")) | ||
| return engine | ||
|
|
||
| mocker.patch("ForcepointSecurityManagementCenter.Engine", side_effect=mock_engine_side_effect) | ||
|
|
||
| with pytest.raises(DemistoException, match=r"Failed to initiate refresh tasks for engines \(engine2\)"): | ||
| engine_refresh_command(args) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
|
|
||
| #### Integrations | ||
|
|
||
| ##### Forcepoint Security Management Center | ||
|
|
||
| - Added support for ***forcepoint-smc-engine-refresh*** command that refreshes the specified engines. use ***forcepoint-smc-engine-list*** command to list the engines in the system. | ||
|
TheL0L marked this conversation as resolved.
Outdated
|
||
Uh oh!
There was an error while loading. Please reload this page.