fix(bitdefender): correct assignPolicy + isolate/unisolate param shapes (live-verified)

Found during the RMM-TEST-MACHINE full-function test (live tenant):
- assignPolicy: assigning a policyId REQUIRES inheritFromAbove:false in the same
  call, else the API rejects with a misleading "inheritFromAbove should not be
  used with policyId" error. Fixed assign_policy to always send it; dropped the
  wrong --inherit-from-above flag.
- isolate/unisolate: the API takes a SINGLE endpointId per call, NOT an
  endpointIds array (errored "not expected"). Client now loops per endpoint.
  unisolate fails while the isolate task is in progress — wait + retry.
- api-reference updated with the live-verified shapes.

Full function test PASSED on RMM-TEST-MACHINE: install(offline kit/SYSTEM) ->
enroll -> move(ZZ-RMM-TEST) -> assign-policy(GPS Base, applied) -> set-label ->
scan -> reconfigure -> isolate -> unisolate -> quarantine/blocklist read ->
managed uninstall(deleteEndpoint). selftest 75/75.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-21 14:26:44 -07:00
parent 6fed424b47
commit 603773cf25
3 changed files with 34 additions and 33 deletions

View File

@@ -420,7 +420,6 @@ def cmd_assign_policy(client, args):
result = client.assign_policy(
args.policy, args.targets,
force_inheritance=args.force_inheritance,
inherit_from_above=args.inherit_from_above,
)
_emit({"assignedPolicy": args.policy, "targets": args.targets,
"result": result}, args.json, _print_kv)
@@ -1032,8 +1031,6 @@ def build_parser() -> argparse.ArgumentParser:
help="One or more endpoint/group ids.")
sp.add_argument("--force-inheritance", action="store_true",
help="Force policy inheritance to sub-items.")
sp.add_argument("--inherit-from-above", action="store_true",
help="Inherit the policy from the parent group.")
sp.add_argument("--confirm", action="store_true")
sp = sub.add_parser("account-create", help="Create a console account (gated).",

View File

@@ -612,14 +612,14 @@ class GravityZoneClient:
v1.2 takes an ARRAY `endpointIds` (max 1000) and returns an array of
task ids. STATE-CHANGING — gate behind --confirm at the call site.
"""
if len(endpoint_ids) > 1000:
raise GravityZoneError(
"isolate_endpoints accepts at most 1000 endpoint ids per call "
f"(got {len(endpoint_ids)})."
)
return self._jsonrpc_request(
"incidents", "createIsolateEndpointTask", {"endpointIds": endpoint_ids}
)
# VERIFIED LIVE 2026-06-21: the API takes a SINGLE `endpointId` per call
# (NOT an `endpointIds` array — that errors "not expected"). Loop for many.
results = []
for eid in endpoint_ids:
results.append(self._jsonrpc_request(
"incidents", "createIsolateEndpointTask", {"endpointId": eid}
))
return results if len(results) != 1 else results[0]
def restore_endpoints_from_isolation(self, endpoint_ids: list[str]) -> Any:
"""Un-isolate endpoints (incidents.createRestoreEndpointFromIsolationTask).
@@ -627,16 +627,16 @@ class GravityZoneClient:
v1.2 takes an ARRAY `endpointIds` (max 1000) and returns an array of
task ids. STATE-CHANGING — gate behind --confirm at the call site.
"""
if len(endpoint_ids) > 1000:
raise GravityZoneError(
"restore_endpoints_from_isolation accepts at most 1000 endpoint "
f"ids per call (got {len(endpoint_ids)})."
)
return self._jsonrpc_request(
"incidents",
"createRestoreEndpointFromIsolationTask",
{"endpointIds": endpoint_ids},
)
# VERIFIED LIVE 2026-06-21: single `endpointId` per call (not an array).
# Note: fails if the isolation task is still in progress — wait + retry.
results = []
for eid in endpoint_ids:
results.append(self._jsonrpc_request(
"incidents",
"createRestoreEndpointFromIsolationTask",
{"endpointId": eid},
))
return results if len(results) != 1 else results[0]
def add_to_blocklist(
self,
@@ -689,22 +689,26 @@ class GravityZoneClient:
policy_id: str,
target_ids: list[str],
force_inheritance: bool = False,
inherit_from_above: bool = False,
) -> Any:
"""Assign an existing policy to endpoints/groups (network.assignPolicy).
Param shape VERIFIED against the official docs + live validation probe
(2026-06-21): `policyId` and `targetIds` (endpoint/container ids) are
required; `forcePolicyInheritance` and `inheritFromAbove` are optional
bools. NOTE: the policy is NOT applied to targets that already carry an
ENFORCED policy. STATE-CHANGING — gate at the call site behind --confirm.
VERIFIED LIVE 2026-06-21: assigning a `policyId` REQUIRES sending
`inheritFromAbove=false` in the same call. An inheriting target defaults
inheritFromAbove to true, so omitting it makes the API reject the call
with a misleading "inheritFromAbove should not be used with policyId"
error. `forcePolicyInheritance` optionally pushes the policy down to
sub-items. (To make a target INHERIT instead, call with
{targetIds, inheritFromAbove:true} and no policyId via `raw`.)
STATE-CHANGING — gate at the call site behind --confirm.
Docs: bitdefender.com/business/support/en/77212-924802-assignpolicy.html
"""
params: dict = {"policyId": policy_id, "targetIds": target_ids}
params: dict = {
"policyId": policy_id,
"targetIds": target_ids,
"inheritFromAbove": False,
}
if force_inheritance:
params["forcePolicyInheritance"] = True
if inherit_from_above:
params["inheritFromAbove"] = True
return self._jsonrpc_request("network", "assignPolicy", params)
def list_scan_tasks(