Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@
how a consumer would use the library or CLI tool (e.g. adding unit tests, updating documentation, etc) are not captured
here.

## 2.10.0 - 2026-01-27

### Added
- Added support for filtering agents by agent health state modification date.
- Added the `agent_health_modified_in_last_days` parameter in the SDK's agent get methods to filter agents by health state modification date.
- Added the `--agent-health-modified-within-days` option to the CLI's `incydr agents list` command to filter agents by health state modification date.
- Added the `agent_health_modification_date` field to the Agent response model.

## 2.9.0 - 2026-01-22

### Added
Expand Down
8 changes: 8 additions & 0 deletions src/_incydr_cli/cmds/agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,20 @@ def agents():
"of the given health issue type(s). Health issue types include the following: NOT_CONNECTING, NOT_SENDING_SECURITY_EVENTS, SECURITY_INGEST_REJECTED, MISSING_MACOS_PERMISSION_FULL_DISK_ACCESS, MISSING_MACOS_PERMISSION_ACCESSIBILITY.",
cls=incompatible_with("healthy"),
)
@click.option(
"--agent-health-modified-within-days",
type=int,
default=None,
help="Filter agents that have had agent health modified in the last N days (starting from midnight this morning), where N is the value of the parameter.",
)
@table_format_option
@columns_option
@logging_options
def list_(
active: bool = None,
healthy: bool = None,
unhealthy: str = None,
agent_health_modified_within_days: int = None,
format_: TableFormat = None,
columns: str = None,
):
Expand All @@ -90,6 +97,7 @@ def list_(
active=active,
agent_healthy=agent_healthy,
agent_health_issue_types=health_issues,
agent_health_modified_in_last_days=agent_health_modified_within_days,
)

if format_ == TableFormat.table:
Expand Down
2 changes: 1 addition & 1 deletion src/_incydr_sdk/__version__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2022-present Code42 Software <integrations@code42.com>
#
# SPDX-License-Identifier: MIT
__version__ = "2.9.0"
__version__ = "2.10.0"
5 changes: 5 additions & 0 deletions src/_incydr_sdk/agents/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def get_page(
page_size: int = 500,
agent_healthy: bool = None,
agent_health_issue_types: Union[List[str], str] = None,
agent_health_modified_in_last_days: Optional[int] = None,
user_id: str = None,
) -> AgentsPage:
"""
Expand All @@ -56,6 +57,7 @@ def get_page(
* **sort_key**: [`SortKeys`][agents-sort-keys] - Values on which the response will be sorted. Defaults to agent name.
* **agent_healthy**: `bool | None` - Optionally retrieve agents with this health status. Agents that have no health issue types are considered healthy.
* **agent_health_issue_types**: `List[str] | str` - Optionally retrieve agents that have (at least) any of the given issue type(s). Health issue types include the following: `NOT_CONNECTING`, `NOT_SENDING_SECURITY_EVENTS`, `SECURITY_INGEST_REJECTED`, `MISSING_MACOS_PERMISSION_FULL_DISK_ACCESS`, `MISSING_MACOS_PERMISSION_ACCESSIBILITY`.
* **agent_health_modified_in_last_days**: `int | None` - Optionally retrieve agents that have had their agent health modified in the last N days.
* **user_id**: `str` - Optionally retrieve only agents associated with this user ID.

**Returns**: An [`AgentsPage`][agentspage-model] object.
Expand All @@ -67,6 +69,7 @@ def get_page(
anyOfAgentHealthIssueTypes=[agent_health_issue_types]
if isinstance(agent_health_issue_types, str)
else agent_health_issue_types,
agentHealthModifiedInLastDays=agent_health_modified_in_last_days,
srtDir=sort_dir,
srtKey=sort_key,
pageSize=page_size,
Expand All @@ -85,6 +88,7 @@ def iter_all(
page_size: int = 500,
agent_healthy: bool = None,
agent_health_issue_types: List[str] = None,
agent_health_modified_in_last_days: Optional[int] = None,
user_id: str = None,
) -> Iterator[Agent]:
"""
Expand All @@ -100,6 +104,7 @@ def iter_all(
agent_type=agent_type,
agent_healthy=agent_healthy,
agent_health_issue_types=agent_health_issue_types,
agent_health_modified_in_last_days=agent_health_modified_in_last_days,
sort_dir=sort_dir,
sort_key=sort_key,
page_num=page_num,
Expand Down
5 changes: 5 additions & 0 deletions src/_incydr_sdk/agents/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class Agent(ResponseModel):
* **active**: `bool` If the agent status is active.
* **agent_type**: [`AgentType`][agent-type] The type of agent.
* **agent_health_issue_types: `List[str]` List of health issues with the agent. Health issue types include the following: `NOT_CONNECTING`, `NOT_SENDING_SECURITY_EVENTS`, `SECURITY_INGEST_REJECTED`, `MISSING_MACOS_PERMISSION_FULL_DISK_ACCESS`, `MISSING_MACOS_PERMISSION_ACCESSIBILITY`.
* **agent_health_modification_date**: `datetime` The time the agent's health state was last modified.
* **app_version**: `str` The app version of the agent.
* **product_version**: `str` The product version of the agent.
* **last_connected**: `datetime` The time the agent last connected to a Code42 Authority server.
Expand All @@ -66,6 +67,9 @@ class Agent(ResponseModel):
active: Optional[bool]
agent_type: Optional[Union[AgentType, str]] = Field(alias="agentType")
agent_health_issue_types: Optional[List[str]] = Field(alias="agentHealthIssueTypes")
agent_health_modification_date: Optional[datetime] = Field(
alias="agentHealthModificationDate"
)
app_version: Optional[str] = Field(alias="appVersion")
product_version: Optional[str] = Field(alias="productVersion")
last_connected: Optional[datetime] = Field(alias="lastConnected")
Expand Down Expand Up @@ -102,6 +106,7 @@ class QueryAgentsRequest(BaseModel):
agentType: Optional[Union[AgentType, str]] = None
agentHealthy: Optional[bool] = None
anyOfAgentHealthIssueTypes: Optional[List[str]] = None
agentHealthModifiedInLastDays: Optional[int] = None
srtKey: Optional[Union[SortKeys, str]] = None
srtDir: Optional[str] = None
pageSize: Optional[int] = None
Expand Down
63 changes: 63 additions & 0 deletions tests/test_agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"active": True,
"agentType": "COMBINED",
"agentHealthIssueTypes": ["NOT_CONNECTING"],
"agentHealthModificationDate": "2022-07-14T17:03:22.123000Z",
"appVersion": "1.0",
"productVersion": "2.0",
"lastConnected": "2022-07-14T17:05:44.524000Z",
Expand All @@ -46,6 +47,7 @@
"active": True,
"agentType": "COMBINED",
"agentHealthIssueTypes": [],
"agentHealthModificationDate": "2022-07-14T17:02:15.456000Z",
"appVersion": "1.0",
"productVersion": "2.0",
"lastConnected": "2022-07-14T17:05:44.524000Z",
Expand All @@ -65,6 +67,7 @@
"active": True,
"agentType": "COMBINED",
"agentHealthIssueTypes": [],
"agentHealthModificationDate": "2022-07-14T17:01:30.789000Z",
"appVersion": "1.0",
"productVersion": "2.0",
"lastConnected": "2022-07-14T17:05:44.524000Z",
Expand Down Expand Up @@ -117,6 +120,9 @@ def test_get_agent_returns_expected_data(mock_get_agent):
assert agent.modification_date == datetime.fromisoformat(
TEST_AGENT_1["modificationDate"].replace("Z", "+00:00")
)
assert agent.agent_health_modification_date == datetime.fromisoformat(
TEST_AGENT_1["agentHealthModificationDate"].replace("Z", "+00:00")
)


def test_get_page_when_default_query_params_returns_expected_data(
Expand Down Expand Up @@ -165,6 +171,35 @@ def test_get_page_when_custom_query_params_returns_expected_data(
assert page.total_count == len(page.agents) == 2


def test_get_page_when_agent_health_modified_in_last_days_passed_makes_expected_call(
httpserver_auth: HTTPServer,
):
query = {
"agentHealthModifiedInLastDays": 7,
"srtKey": "NAME",
"srtDir": "ASC",
"pageSize": 500,
"page": 1,
}

agents_data = {
"agents": [TEST_AGENT_1, TEST_AGENT_2],
"totalCount": 2,
"pageSize": 500,
"page": 1,
}
httpserver_auth.expect_request(
uri="/v1/agents", method="GET", query_string=urlencode(query)
).respond_with_json(agents_data)

client = Client()
page = client.agents.v1.get_page(agent_health_modified_in_last_days=7)
assert isinstance(page, AgentsPage)
assert page.agents[0].json() == json.dumps(TEST_AGENT_1, separators=(",", ":"))
assert page.agents[1].json() == json.dumps(TEST_AGENT_2, separators=(",", ":"))
assert page.total_count == len(page.agents) == 2


def test_iter_all_when_default_params_returns_expected_data(
httpserver_auth: HTTPServer,
):
Expand Down Expand Up @@ -346,6 +381,34 @@ def test_cli_list_when_unhealthy_option_passed_with_string_parses_issue_types_co
assert result.exit_code == 0


def test_cli_list_when_health_modified_days_option_passed_makes_expected_call(
httpserver_auth: HTTPServer, runner
):
query = {
"agentHealthModifiedInLastDays": 7,
"srtKey": "NAME",
"srtDir": "ASC",
"pageSize": 500,
"page": 1,
}

agents_data = {
"agents": [TEST_AGENT_1, TEST_AGENT_2],
"totalCount": 2,
"pageSize": 500,
"page": 1,
}
httpserver_auth.expect_request(
uri="/v1/agents", method="GET", query_string=urlencode(query)
).respond_with_json(agents_data)

result = runner.invoke(
incydr, ["agents", "list", "--agent-health-modified-within-days", "7"]
)
httpserver_auth.check()
assert result.exit_code == 0


def test_cli_show_when_custom_params_makes_expected_call(
httpserver_auth: HTTPServer, runner, mock_get_agent
):
Expand Down
3 changes: 3 additions & 0 deletions tests/test_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"active": True,
"agentType": "COMBINED",
"agentHealthIssueTypes": ["NOT_CONNECTING"],
"agentHealthModificationDate": "2022-07-14T17:03:22.123000Z",
"appVersion": "1.0",
"productVersion": "2.0",
"lastConnected": "2022-07-14T17:05:44.524000Z",
Expand All @@ -54,6 +55,7 @@
"active": True,
"agentType": "COMBINED",
"agentHealthIssueTypes": [],
"agentHealthModificationDate": "2022-07-14T17:02:15.456000Z",
"appVersion": "1.0",
"productVersion": "2.0",
"lastConnected": "2022-07-14T17:05:44.524000Z",
Expand All @@ -73,6 +75,7 @@
"active": True,
"agentType": "COMBINED",
"agentHealthIssueTypes": [],
"agentHealthModificationDate": "2022-07-14T17:01:30.789000Z",
"appVersion": "1.0",
"productVersion": "2.0",
"lastConnected": "2022-07-14T17:05:44.524000Z",
Expand Down