Skip to content

Commit ac14b28

Browse files
committed
[fix] Update docs and address coderabbit comments
1 parent 8efe276 commit ac14b28

6 files changed

Lines changed: 75 additions & 62 deletions

File tree

docs/user/rest-api.rst

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -507,7 +507,6 @@ Parameter Description
507507
(optional)
508508
``group`` Device group UUID (optional)
509509
``location`` Location UUID (optional)
510-
``execute_all`` Set to ``true`` to target all devices (optional)
511510
================ =========================================================
512511

513512
Execute a Mass Command
@@ -534,7 +533,6 @@ Parameter Description
534533
``devices`` List of device UUIDs (optional)
535534
``group`` Device group UUID (optional)
536535
``location`` Location UUID (optional)
537-
``execute_all`` Set to ``true`` to target all devices (optional)
538536
================ ========================================================
539537

540538
**Available Command Types:**
@@ -550,8 +548,7 @@ input formats.
550548
"organization": "org-uuid",
551549
"type": "custom",
552550
"input": {"command": "uptime"},
553-
"label": "Check uptime",
554-
"execute_all": true
551+
"label": "Check uptime"
555552
}
556553
557554
**Example request:**
@@ -566,8 +563,7 @@ input formats.
566563
"organization": "org-uuid",
567564
"type": "custom",
568565
"input": {"command": "uptime"},
569-
"label": "Check uptime",
570-
"execute_all": true
566+
"label": "Check uptime"
571567
}'
572568
573569
**Response:** ``201 Created`` with the batch command UUID.

docs/user/shell-commands.rst

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,11 +201,10 @@ You can target devices using any combination of the following:
201201
- ``devices``: Explicit list of device UUIDs.
202202
- ``group``: All devices belonging to a device group.
203203
- ``location``: All devices at a specific location.
204-
- ``execute_all``: All devices in the organization.
205204

206-
If ``devices`` is provided as an empty list and ``execute_all`` is
207-
``false``, the request will be rejected. To target all devices, set
208-
``execute_all`` to ``true``.
205+
If no targeting options are provided, the command targets all devices in
206+
the organization. If ``devices`` is provided as an empty list, the request
207+
is rejected because no devices match.
209208

210209
Refer to the :ref:`Batch Command API <controller_batch_command_api>`
211210
documentation for the available endpoints, request parameters, and

openwisp_controller/connection/api/serializers.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ def _redact_skipped_devices(self, data, request):
203203
user_org_ids = set(str(org) for org in request.user.organizations_managed)
204204
user_org_device_uuids = set(
205205
str(pk)
206-
for pk, in Device.objects.filter(
206+
for pk in Device.objects.filter(
207207
pk__in=device_pks, organization_id__in=user_org_ids
208208
).values_list("pk", flat=True)
209209
)
@@ -215,13 +215,19 @@ def _redact_skipped_devices(self, data, request):
215215
org_name = device_org_map.get(
216216
uuid.UUID(device_pk), "some other organization"
217217
)
218-
redacted[f"{org_name} device"] = [
219-
f"restricted to {org_name} managers and users"
218+
redacted[_("{org_name} device").format(org_name=org_name)] = [
219+
_("restricted to {org_name} managers and users").format(
220+
org_name=org_name
221+
)
220222
]
221223
data["skipped_devices"] = redacted
222224

223225
def to_representation(self, instance):
224226
data = super().to_representation(instance)
227+
# The raw password is visible in API responses between
228+
# when the batch is saved and when the Celery task runs _clean_sensitive_info().
229+
if instance.type == "change_password":
230+
data["input"] = {"password": "********"}
225231
request = self.context.get("request")
226232
if (
227233
instance.organization_id is None
@@ -263,6 +269,10 @@ class BatchCommandDetailSerializer(BatchCommandSerializer):
263269

264270
def to_representation(self, instance):
265271
data = super().to_representation(instance)
272+
# The raw password is visible in API responses between
273+
# when the batch is saved and when the Celery task runs _clean_sensitive_info().
274+
if instance.type == "change_password":
275+
data["input"] = {"password": "********"}
266276
request = self.context.get("request")
267277
if (
268278
instance.organization_id is None
@@ -274,7 +284,7 @@ def to_representation(self, instance):
274284
if device_uuids:
275285
visible_uuids = set(
276286
str(pk)
277-
for pk, in Device.objects.filter(
287+
for pk in Device.objects.filter(
278288
pk__in=device_uuids, organization_id__in=user_org_ids
279289
).values_list("pk", flat=True)
280290
)

openwisp_controller/connection/api/views.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ def post(self, request):
163163
getattr(e, "message_dict", e.messages),
164164
status=status.HTTP_400_BAD_REQUEST,
165165
)
166-
return Response({"batch": str(batch.pk)}, status=201)
166+
return Response({"batch": str(batch.pk)}, status=status.HTTP_201_CREATED)
167167

168168
def get(self, request):
169169
serializer = self.get_serializer(

openwisp_controller/connection/base/models.py

Lines changed: 48 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -977,7 +977,7 @@ def create_commands(self):
977977
command.full_clean()
978978
with transaction.atomic():
979979
command.save()
980-
except Exception as e:
980+
except ValidationError as e:
981981
self.skipped_devices[str(device.pk)] = (
982982
e.messages if hasattr(e, "messages") else [str(e)]
983983
)
@@ -1006,48 +1006,52 @@ def calculate_and_update_status(self):
10061006
- All commands completed successfully: status set to "success".
10071007
- Status unchanged: no database write performed.
10081008
"""
1009-
stats = self.batch_commands.aggregate(
1010-
total_operations=models.Count("id"),
1011-
in_progress=models.Count(
1012-
models.Case(
1013-
models.When(status="in-progress", then=1),
1014-
output_field=models.IntegerField(),
1015-
)
1016-
),
1017-
completed=models.Count(
1018-
models.Case(
1019-
models.When(~models.Q(status="in-progress"), then=1),
1020-
output_field=models.IntegerField(),
1021-
)
1022-
),
1023-
successful=models.Count(
1024-
models.Case(
1025-
models.When(status="success", then=1),
1026-
output_field=models.IntegerField(),
1027-
)
1028-
),
1029-
failed=models.Count(
1030-
models.Case(
1031-
models.When(status="failed", then=1),
1032-
output_field=models.IntegerField(),
1033-
)
1034-
),
1035-
)
1036-
if stats["total_operations"] == 0:
1037-
if self.skipped_devices:
1009+
with transaction.atomic():
1010+
batch = self.__class__.objects.select_for_update().get(pk=self.pk)
1011+
stats = batch.batch_commands.aggregate(
1012+
total_operations=models.Count("id"),
1013+
in_progress=models.Count(
1014+
models.Case(
1015+
models.When(status="in-progress", then=1),
1016+
output_field=models.IntegerField(),
1017+
)
1018+
),
1019+
completed=models.Count(
1020+
models.Case(
1021+
models.When(~models.Q(status="in-progress"), then=1),
1022+
output_field=models.IntegerField(),
1023+
)
1024+
),
1025+
successful=models.Count(
1026+
models.Case(
1027+
models.When(status="success", then=1),
1028+
output_field=models.IntegerField(),
1029+
)
1030+
),
1031+
failed=models.Count(
1032+
models.Case(
1033+
models.When(status="failed", then=1),
1034+
output_field=models.IntegerField(),
1035+
)
1036+
),
1037+
)
1038+
if stats["total_operations"] == 0:
1039+
if batch.skipped_devices:
1040+
new_status = "failed"
1041+
else:
1042+
new_status = "idle"
1043+
elif stats["in_progress"] > 0:
1044+
new_status = "in-progress"
1045+
elif stats["failed"] > 0:
10381046
new_status = "failed"
1047+
elif (
1048+
stats["successful"] > 0
1049+
and stats["completed"] == stats["total_operations"]
1050+
and not batch.skipped_devices
1051+
):
1052+
new_status = "success"
10391053
else:
1040-
new_status = "idle"
1041-
elif stats["in_progress"] > 0:
1042-
new_status = "in-progress"
1043-
elif stats["failed"] > 0:
1044-
new_status = "failed"
1045-
elif (
1046-
stats["successful"] > 0 and stats["completed"] == stats["total_operations"]
1047-
):
1048-
new_status = "success"
1049-
else:
1050-
new_status = self.status
1051-
if self.status != new_status:
1052-
self.status = new_status
1053-
self.save(update_fields=["status"])
1054+
new_status = batch.status
1055+
if batch.status != new_status:
1056+
batch.status = new_status
1057+
batch.save(update_fields=["status"])

openwisp_controller/connection/tasks.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,15 @@ def launch_batch_command(self, batch_id):
108108
return
109109
try:
110110
batch.create_commands()
111-
except Exception as e:
111+
except Exception:
112+
batch._clean_sensitive_info()
112113
batch.status = "failed"
113-
batch.save(update_fields=["status"])
114+
update_fields = ["status"]
115+
if batch.type == "change_password":
116+
update_fields.append("input")
117+
batch.save(update_fields=update_fields)
114118
logger.exception(
115-
f"An exception was raised while executing batch " f"command {batch_id}: {e}"
119+
f"An exception was raised while executing batch command {batch_id}"
116120
)
117121

118122

0 commit comments

Comments
 (0)