feat(BA-5737): add RBAC-enforced VFolder create mutations#11139
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a project-scoped mutation surface for vfolder v2 so project admin flows can create/delete vfolders via PROJECT-scoped RBAC (consistent with the existing projectVfolders query).
Changes:
- Introduce PROJECT-scoped vfolder actions (
Create/Delete/BulkDelete*InProject) and wire them throughScopeActionProcessorusing scope RBAC validators. - Expose new GraphQL mutations (
createProjectVfolderV2,deleteProjectVfolderV2,bulkDeleteProjectVfoldersV2) backed by the new adapter methods. - Add unit/component test coverage for delete/bulk-delete project-scoped paths and service routing.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/manager/services/vfolder/test_vfolder_in_project_service.py | Unit tests for service delegation and project membership checks. |
| tests/component/vfolder_v2/test_vfolder_project_mutations.py | Component tests exercising RBAC + service behavior for project-scoped deletes. |
| src/ai/backend/manager/services/vfolder/services/vfolder.py | Adds create_in_project, delete_in_project, bulk_delete_in_project service methods. |
| src/ai/backend/manager/services/vfolder/processors/vfolder.py | Registers new PROJECT-scoped action processors and supported action specs. |
| src/ai/backend/manager/services/vfolder/actions/vfolder_in_project.py | New PROJECT-scoped action/result dataclasses for create/delete/bulk-delete. |
| src/ai/backend/manager/api/gql/vfolder_v2/resolver/mutation.py | Adds new GraphQL mutations for project-scoped create/delete/bulk-delete. |
| src/ai/backend/manager/api/gql/vfolder_v2/resolver/init.py | Exports the new mutation resolvers. |
| src/ai/backend/manager/api/gql/vfolder_v2/init.py | Re-exports new vfolder v2 mutations for schema wiring. |
| src/ai/backend/manager/api/gql/schema.py | Adds new mutation fields to the GraphQL Mutation type. |
| src/ai/backend/manager/api/adapters/vfolder.py | Adds adapter entrypoints that dispatch project-scoped actions to processors. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
c8a7a42 to
14fd0ed
Compare
|
I remembered there was a discussion about not using VFolderV2 in GraphQL because it would interfere with the agentic workflow. |
e2e94f0 to
6021bea
Compare
6021bea to
82bb6c1
Compare
cb815be to
eb22e8e
Compare
638554d to
3de71a7
Compare
a95a4d9 to
9e1e8e9
Compare
5f76a19 to
2e5bb7c
Compare
Co-authored-by: octodog <mu001@lablup.com>
…icated input type Introduce CreateVFolderInProjectInput (DTO + GQL) without project_id field, since project_id is passed as a separate mutation argument matching the existing project-scoped pattern. Resolves PR review feedback about duplicate project_id in input and mutation args. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: octodog <mu001@lablup.com>
- Use VFolderUsageModeGQL / VFolderMountPermissionGQL enums in the
CreateVFolderInProjectInputGQL type instead of raw `str`.
- Extract shared creation steps into private VFolderService helpers
(_load_user, _check_user_role_for_group, _check_ownership_allowed,
_check_name_uniqueness, _do_create_vfolder) so create_v2 and
create_in_project compose them differently.
- Fix gaps in create_in_project that were not present in create_v2:
missing name uniqueness check, missing allowed_vfolder_types check,
and creator field storing `str(user_uuid)` instead of `user.email`.
- Add component RBAC denial test for POST /v2/vfolders/projects/{id}.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: octodog <mu001@lablup.com>
…pers Add _resolve_host, _check_name_parameter, _resolve_project_info, _check_user_vfolder_quota, _check_group_vfolder_quota, _determine_ownership, and _check_model_store_usage_mode. Both create_v2 and create_in_project now compose the shared pieces they need. _determine_ownership stays exclusive to create_v2 since create_in_project is always group-owned and intentionally skips the legacy UserRole gate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Align with the TerminateSessionsInProjectInput precedent: the *InProject input DTO carries project_id as a field, and the adapter takes a single DTO (no separate project_id parameter). GQL mutation drops the standalone projectId argument; REST handler overrides body.project_id with the URL path segment so the URL remains authoritative. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes the _do_create_vfolder helper and the two dead-code try/excepts it wrapped. aiohttp.ClientResponseError never escapes StorageProxyHTTPClient (non-2xx is translated to BackendAIError subclasses before returning), and sa_exc.DataError is unreachable given upstream Pydantic input validation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
b731e45 to
35d630d
Compare
Promotes the ad-hoc (uuid, int, int, ProjectType) tuple returned by VfolderRepository.get_group_resource_info into a frozen dataclass in data/group/types.py so the type crosses the repo→service boundary self-describing. Drops the service-local wrapper and its tuple unpacking. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Renames CreateVFolderInProjectInput to scope-agnostic CreateVFolderInScopeInput
and drops its project_id field. The owning scope is now supplied by the
transport layer — a path segment for REST (/v2/vfolders/projects/{project_id}/create)
and a separate mutation argument for GraphQL (createVFolderInProject(projectId, input)).
This keeps the body reusable if other scope types (user, domain) gain their own
scoped-create endpoints later, mirrors the existing scoped-search URL pattern
/v2/{entity}/{scope_type}/{scope_id}/{action}, and makes the RBAC scope visible
at the router level for audit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds test_superadmin_succeeds and test_regular_user_with_permission_succeeds to exercise the happy path through ScopeActionProcessor RBAC. The regular-user case seeds a Role with PROJECT-scoped VFOLDER:CREATE and asserts the service completes end-to-end. Fixes the stale endpoint-path docstring now that the route carries a /create suffix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The repo mock previously returned a raw tuple; switch to a ProjectResourceInfo instance so the tests track the new repo return type. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: octodog <mu001@lablup.com>
Align field names with the class name and type: group_uuid -> project_id, group_type -> project_type. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| result = await self._adapter.restore(path.parsed.vfolder_id) | ||
| return APIResponse.build(status_code=HTTPStatus.OK, response_model=result) | ||
|
|
||
| async def project_create( |
There was a problem hiding this comment.
I understand the intent to follow the convention, but project_create keeps reading as if it creates a project itself.
Would it be better to align it as something like create_in_project?
Summary
createProjectVFolderInProjectGraphQL mutations so project admin flows have a consistent project-scoped mutation surface alongside the existingprojectVfoldersquery.Test plan
pants fmt :: && pants fix :: && pants lint --changed-since=origin/main— greenResolves BA-5737
📚 Documentation preview 📚: https://sorna--11139.org.readthedocs.build/en/11139/
📚 Documentation preview 📚: https://sorna-ko--11139.org.readthedocs.build/ko/11139/