-
-
Notifications
You must be signed in to change notification settings - Fork 299
[feature] BatchCommand model and REST APIs for async batch command execution #1395
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: gsoc26-mass-commands
Are you sure you want to change the base?
Changes from all commits
5908e25
5d05fa9
be4cbca
f288b53
be6612e
4152c81
8751198
9ad0be9
9360ec7
cdddbd9
6c0f66c
6e89ba9
e75f12b
8714e51
49f0827
4474195
9add005
05e8d94
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 |
|---|---|---|
|
|
@@ -481,6 +481,115 @@ Get Command Details | |
|
|
||
| GET /api/v1/controller/device/{device_id}/command/{command_id}/ | ||
|
|
||
| .. _controller_batch_command_api: | ||
|
|
||
| Dry-Run Batch Command | ||
| ~~~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| .. code-block:: text | ||
|
|
||
| GET /api/v1/controller/batch-command/execute/ | ||
|
|
||
| Returns the list of devices that would be targeted without executing | ||
| anything. Useful for previewing which devices are affected. | ||
|
|
||
| **Query Parameters:** | ||
|
|
||
| ================ ======================================================== | ||
| Parameter Description | ||
| ================ ======================================================== | ||
| ``organization`` Organization UUID (optional) | ||
| ``type`` Command type (optional for dry-run) | ||
| ``input`` Input data for the command (optional for dry-run) | ||
|
Member
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. If |
||
| ``devices`` Repeated ``devices`` query parameter, each a device UUID | ||
| (optional) | ||
| ``group`` Device group UUID (optional) | ||
| ``location`` Location UUID (optional) | ||
| ``execute_all`` Set to ``true`` to target all devices (optional) | ||
| ================ ======================================================== | ||
|
|
||
| Execute a Batch Command | ||
| ~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| .. code-block:: text | ||
|
|
||
| POST /api/v1/controller/batch-command/execute/ | ||
|
|
||
| Creates and executes a batch command on the targeted devices. | ||
|
|
||
| **Request Parameters:** | ||
|
|
||
| ================ ======================================================== | ||
| Parameter Description | ||
| ================ ======================================================== | ||
| ``organization`` Organization UUID (optional for superusers) | ||
| ``type`` Type of command to execute (**required**) | ||
| ``input`` Input data for the command (**conditionally required** — | ||
| depends on command type) | ||
| ``label`` A short label to identify this batch command | ||
| (**required**) | ||
| ``notes`` Optional notes (optional) | ||
| ``devices`` List of device UUIDs (optional) | ||
| ``group`` Device group UUID (optional) | ||
| ``location`` Location UUID (optional) | ||
| ``execute_all`` Set to ``true`` to target all devices (optional) | ||
| ================ ======================================================== | ||
|
|
||
| **Available Command Types:** | ||
|
|
||
| See :ref:`controller_execute_command_api` for available command types and | ||
| input formats. | ||
|
|
||
| **Example payload:** | ||
|
|
||
| .. code-block:: json | ||
|
|
||
| { | ||
| "organization": "org-uuid", | ||
| "type": "custom", | ||
| "input": {"command": "uptime"}, | ||
| "label": "Check uptime", | ||
| "execute_all": true | ||
| } | ||
|
|
||
| **Example request:** | ||
|
|
||
| .. code-block:: shell | ||
|
|
||
| curl -X POST \ | ||
| http://127.0.0.1:8000/api/v1/controller/batch-command/execute/ \ | ||
| -H 'authorization: Bearer yoursecretauthtoken' \ | ||
|
Member
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. This placeholder is locally consistent with the single-device command example, but the REST API page is inconsistent overall because it also uses hash-like literal tokens and |
||
| -H 'content-type: application/json' \ | ||
| -d '{ | ||
| "organization": "org-uuid", | ||
| "type": "custom", | ||
| "input": {"command": "uptime"}, | ||
| "label": "Check uptime", | ||
| "execute_all": true | ||
| }' | ||
|
|
||
| **Response:** ``201 Created`` with the batch command UUID. | ||
|
|
||
| List Batch Commands | ||
| ~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| .. code-block:: text | ||
|
|
||
| GET /api/v1/controller/batch-command/ | ||
|
|
||
| Returns a paginated list of batch commands with device count and skipped | ||
| device information. | ||
|
|
||
| Get Batch Command Detail | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| .. code-block:: text | ||
|
|
||
| GET /api/v1/controller/batch-command/{id}/ | ||
|
|
||
| Returns detailed information about a batch command, including the list of | ||
| targeted devices. | ||
|
|
||
| List Device Groups | ||
| ~~~~~~~~~~~~~~~~~~ | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,7 @@ | |
| DeviceConnection = load_model("connection", "DeviceConnection") | ||
| Credentials = load_model("connection", "Credentials") | ||
| Device = load_model("config", "Device") | ||
| BatchCommand = load_model("connection", "BatchCommand") | ||
|
|
||
|
|
||
| class ValidatedDeviceFieldSerializer(ValidatedModelSerializer): | ||
|
|
@@ -43,6 +44,10 @@ class CommandSerializer(ValidatedDeviceFieldSerializer): | |
| required=False, | ||
| pk_field=serializers.UUIDField(format="hex_verbose"), | ||
| ) | ||
| batch_command = serializers.PrimaryKeyRelatedField( | ||
| read_only=True, | ||
| pk_field=serializers.UUIDField(format="hex_verbose"), | ||
| ) | ||
|
|
||
| def __init__(self, *args, **kwargs): | ||
| super().__init__(*args, **kwargs) | ||
|
|
@@ -115,3 +120,117 @@ class Meta: | |
| "is_working": {"read_only": True}, | ||
| } | ||
| read_only_fields = ("created", "modified") | ||
|
|
||
|
|
||
| class BatchCommandExecuteSerializer( | ||
| FilterSerializerByOrgManaged, serializers.ModelSerializer | ||
| ): | ||
| input = serializers.JSONField(allow_null=True, required=False) | ||
| devices = serializers.PrimaryKeyRelatedField( | ||
| many=True, | ||
| queryset=Device.objects.all(), | ||
| required=False, | ||
| allow_empty=True, | ||
|
Member
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. Question about the API contract: are we sure that accepting |
||
| pk_field=serializers.UUIDField(format="hex_verbose"), | ||
| ) | ||
| execute_all = serializers.BooleanField(required=False, default=False) | ||
|
|
||
| def __init__(self, *args, dry_run=False, **kwargs): | ||
| super().__init__(*args, **kwargs) | ||
| if dry_run: | ||
| self._skip_target_validation = True | ||
| self.fields["type"].required = False | ||
| self.fields["label"].required = False | ||
| else: | ||
| self._skip_target_validation = False | ||
|
|
||
| class Meta: | ||
| model = BatchCommand | ||
| fields = ( | ||
| "organization", | ||
| "type", | ||
| "input", | ||
| "label", | ||
| "notes", | ||
| "devices", | ||
| "group", | ||
| "location", | ||
| "execute_all", | ||
| ) | ||
| extra_kwargs = { | ||
| "organization": {"required": False, "allow_null": True}, | ||
| } | ||
|
|
||
| def validate(self, data): | ||
| org = data.get("organization") | ||
| execute_all = data.get("execute_all", False) | ||
| devices = data.get("devices") | ||
| group = data.get("group") | ||
| location = data.get("location") | ||
| if not org and not self.context["request"].user.is_superuser: | ||
| raise serializers.ValidationError( | ||
| _("Only superusers can execute batch commands without an organization.") | ||
| ) | ||
|
dee077 marked this conversation as resolved.
|
||
| if not self._skip_target_validation: | ||
| if ( | ||
| not execute_all | ||
| and not org | ||
| and not devices | ||
| and not group | ||
| and not location | ||
| ): | ||
| raise serializers.ValidationError( | ||
| _( | ||
| "Specify at least one targeting option " | ||
| "or set execute_all to true." | ||
| ) | ||
| ) | ||
| if devices: | ||
| for device in devices: | ||
| if org and device.organization_id != org.id: | ||
| raise serializers.ValidationError( | ||
| { | ||
| "devices": _( | ||
| "All devices must belong to the same organization." | ||
| ) | ||
| } | ||
| ) | ||
| return data | ||
|
dee077 marked this conversation as resolved.
|
||
|
|
||
|
|
||
| class BatchCommandSerializer(BaseSerializer): | ||
| device_count = serializers.IntegerField(read_only=True) | ||
| skipped_devices = serializers.JSONField(read_only=True) | ||
|
|
||
| class Meta: | ||
| model = BatchCommand | ||
| fields = ( | ||
| "id", | ||
| "organization", | ||
| "status", | ||
| "type", | ||
| "input", | ||
| "label", | ||
| "notes", | ||
| "group", | ||
| "location", | ||
| "device_count", | ||
| "skipped_devices", | ||
|
Member
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. For shared/global batch commands ( |
||
| "created", | ||
| "modified", | ||
| ) | ||
| read_only_fields = ( | ||
| "created", | ||
| "modified", | ||
| ) | ||
|
|
||
|
|
||
| class BatchCommandDetailSerializer(BatchCommandSerializer): | ||
| devices = serializers.PrimaryKeyRelatedField( | ||
| many=True, | ||
| read_only=True, | ||
| pk_field=serializers.UUIDField(format="hex_verbose"), | ||
| ) | ||
|
|
||
| class Meta(BatchCommandSerializer.Meta): | ||
| fields = BatchCommandSerializer.Meta.fields + ("devices",) | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The REST API docs were added, but the related docs requested for this feature are still missing. Please also update
docs/developer/extending.rstwithCONNECTION_BATCHCOMMAND_MODEL, add a first basic version of a new mass commands subsection at the end ofdocs/user/shell-commands.rstlinking to this REST API anchor, and mention mass commands indocs/user/intro.rst(linking to its dedicated subsection inshell-commands.rst.