From 9d745fca75098d76f2ab69ef8049a8d476fe9fc0 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 16 Mar 2026 15:37:46 +0100 Subject: [PATCH] feat(api): expand update_storage to support name, mount_path, host_path, content fields Add support for updating additional storage fields via the API while enforcing read-only restrictions for storages managed by docker-compose or service definitions (only is_preview_suffix_enabled remains editable for those). --- .../Api/ApplicationsController.php | 48 +++++++++++++++++-- openapi.json | 20 +++++++- openapi.yaml | 16 ++++++- 3 files changed, 79 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index a6609eb47..6521ef7ba 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -4005,7 +4005,7 @@ public function storages(Request $request) ), ], requestBody: new OA\RequestBody( - description: 'Storage updated.', + description: 'Storage updated. For read-only storages (from docker-compose or services), only is_preview_suffix_enabled can be updated.', required: true, content: [ new OA\MediaType( @@ -4017,6 +4017,10 @@ public function storages(Request $request) 'id' => ['type' => 'integer', 'description' => 'The ID of the storage.'], 'type' => ['type' => 'string', 'enum' => ['persistent', 'file'], 'description' => 'The type of storage: persistent or file.'], 'is_preview_suffix_enabled' => ['type' => 'boolean', 'description' => 'Whether to add -pr-N suffix for preview deployments.'], + 'name' => ['type' => 'string', 'description' => 'The volume name (persistent only, not allowed for read-only storages).'], + 'mount_path' => ['type' => 'string', 'description' => 'The container mount path (not allowed for read-only storages).'], + 'host_path' => ['type' => 'string', 'nullable' => true, 'description' => 'The host path (persistent only, not allowed for read-only storages).'], + 'content' => ['type' => 'string', 'nullable' => true, 'description' => 'The file content (file only, not allowed for read-only storages).'], ], ), ), @@ -4043,7 +4047,6 @@ public function storages(Request $request) )] public function update_storage(Request $request) { - $allowedFields = ['id', 'type', 'is_preview_suffix_enabled']; $teamId = getTeamIdFromToken(); if (is_null($teamId)) { @@ -4069,9 +4072,14 @@ public function update_storage(Request $request) 'id' => 'required|integer', 'type' => 'required|string|in:persistent,file', 'is_preview_suffix_enabled' => 'boolean', + 'name' => 'string', + 'mount_path' => 'string', + 'host_path' => 'string|nullable', + 'content' => 'string|nullable', ]); - $extraFields = array_diff(array_keys($request->all()), $allowedFields); + $allAllowedFields = ['id', 'type', 'is_preview_suffix_enabled', 'name', 'mount_path', 'host_path', 'content']; + $extraFields = array_diff(array_keys($request->all()), $allAllowedFields); if ($validator->fails() || ! empty($extraFields)) { $errors = $validator->errors(); if (! empty($extraFields)) { @@ -4098,10 +4106,44 @@ public function update_storage(Request $request) ], 404); } + $isReadOnly = $storage->shouldBeReadOnlyInUI(); + $editableOnlyFields = ['name', 'mount_path', 'host_path', 'content']; + $requestedEditableFields = array_intersect($editableOnlyFields, array_keys($request->all())); + + if ($isReadOnly && ! empty($requestedEditableFields)) { + return response()->json([ + 'message' => 'This storage is read-only (managed by docker-compose or service definition). Only is_preview_suffix_enabled can be updated.', + 'read_only_fields' => array_values($requestedEditableFields), + ], 422); + } + + // Always allowed if ($request->has('is_preview_suffix_enabled')) { $storage->is_preview_suffix_enabled = $request->is_preview_suffix_enabled; } + // Only for editable storages + if (! $isReadOnly) { + if ($request->type === 'persistent') { + if ($request->has('name')) { + $storage->name = $request->name; + } + if ($request->has('mount_path')) { + $storage->mount_path = $request->mount_path; + } + if ($request->has('host_path')) { + $storage->host_path = $request->host_path; + } + } else { + if ($request->has('mount_path')) { + $storage->mount_path = $request->mount_path; + } + if ($request->has('content')) { + $storage->content = $request->content; + } + } + } + $storage->save(); return response()->json($storage); diff --git a/openapi.json b/openapi.json index 13f745e11..d1dadcaf0 100644 --- a/openapi.json +++ b/openapi.json @@ -3500,7 +3500,7 @@ } ], "requestBody": { - "description": "Storage updated.", + "description": "Storage updated. For read-only storages (from docker-compose or services), only is_preview_suffix_enabled can be updated.", "required": true, "content": { "application\/json": { @@ -3525,6 +3525,24 @@ "is_preview_suffix_enabled": { "type": "boolean", "description": "Whether to add -pr-N suffix for preview deployments." + }, + "name": { + "type": "string", + "description": "The volume name (persistent only, not allowed for read-only storages)." + }, + "mount_path": { + "type": "string", + "description": "The container mount path (not allowed for read-only storages)." + }, + "host_path": { + "type": "string", + "nullable": true, + "description": "The host path (persistent only, not allowed for read-only storages)." + }, + "content": { + "type": "string", + "nullable": true, + "description": "The file content (file only, not allowed for read-only storages)." } }, "type": "object" diff --git a/openapi.yaml b/openapi.yaml index 7ee406c99..74af3aa13 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -2212,7 +2212,7 @@ paths: schema: type: string requestBody: - description: 'Storage updated.' + description: 'Storage updated. For read-only storages (from docker-compose or services), only is_preview_suffix_enabled can be updated.' required: true content: application/json: @@ -2231,6 +2231,20 @@ paths: is_preview_suffix_enabled: type: boolean description: 'Whether to add -pr-N suffix for preview deployments.' + name: + type: string + description: 'The volume name (persistent only, not allowed for read-only storages).' + mount_path: + type: string + description: 'The container mount path (not allowed for read-only storages).' + host_path: + type: string + nullable: true + description: 'The host path (persistent only, not allowed for read-only storages).' + content: + type: string + nullable: true + description: 'The file content (file only, not allowed for read-only storages).' type: object responses: '200':