Skip to content

Commit a18baf2

Browse files
authored
feat: Add create project tool (#66)
* feat(identity): Add get-project, get-projects tool(#58) - Add get-project, get-projects tool - Test code are updated and passing * chore(identity): ruff format(#58) * chore(identity): ruff format(#58) * fix(identity): Remove _version.py(#58) * feat(identity): Add create project tool(#58) - Add create-project tool - Tests are updated ans passing * fix(identity): Fix unnecessary test codes(#?58)
1 parent d1a13f6 commit a18baf2

File tree

2 files changed

+122
-0
lines changed

2 files changed

+122
-0
lines changed

src/openstack_mcp_server/tools/identity_tools.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ def register_tools(self, mcp: FastMCP):
2828

2929
mcp.tool()(self.get_projects)
3030
mcp.tool()(self.get_project)
31+
mcp.tool()(self.create_project)
3132

3233
def get_regions(self) -> list[Region]:
3334
"""
@@ -269,3 +270,41 @@ def get_project(self, name: str) -> Project:
269270
domain_id=project.domain_id,
270271
parent_id=project.parent_id,
271272
)
273+
274+
def create_project(
275+
self,
276+
name: str,
277+
description: str | None = None,
278+
is_enabled: bool = True,
279+
domain_id: str | None = None,
280+
parent_id: str | None = None,
281+
) -> Project:
282+
"""
283+
Create a new project.
284+
285+
:param name: The name of the project.
286+
:param description: The description of the project.
287+
:param is_enabled: Whether the project is enabled.
288+
:param domain_id: The ID of the domain.
289+
:param parent_id: The ID of the parent project.
290+
291+
:return: The created Project object.
292+
"""
293+
conn = get_openstack_conn()
294+
295+
project = conn.identity.create_project(
296+
name=name,
297+
description=description,
298+
is_enabled=is_enabled,
299+
domain_id=domain_id,
300+
parent_id=parent_id,
301+
)
302+
303+
return Project(
304+
id=project.id,
305+
name=project.name,
306+
description=project.description,
307+
is_enabled=project.is_enabled,
308+
domain_id=project.domain_id,
309+
parent_id=project.parent_id,
310+
)

tests/tools/test_identity_tools.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -853,3 +853,86 @@ def test_get_project_not_found(self, mock_get_openstack_conn_identity):
853853
name_or_id="ProjectOne",
854854
ignore_missing=False,
855855
)
856+
857+
def test_create_project_success_with_all_fields(
858+
self, mock_get_openstack_conn_identity
859+
):
860+
"""Test creating a identity project successfully."""
861+
mock_conn = mock_get_openstack_conn_identity
862+
863+
# Create mock project object
864+
mock_project = Mock()
865+
mock_project.id = "project1111111111111111111111111"
866+
mock_project.name = "ProjectOne"
867+
mock_project.description = "Project One description"
868+
mock_project.is_enabled = True
869+
mock_project.domain_id = "domain1111111111111111111111111"
870+
mock_project.parent_id = "parentproject1111111111111111111"
871+
872+
# Configure mock project.create_project()
873+
mock_conn.identity.create_project.return_value = mock_project
874+
875+
# Test create_project()
876+
identity_tools = self.get_identity_tools()
877+
result = identity_tools.create_project(
878+
name="ProjectOne",
879+
description="Project One description",
880+
is_enabled=True,
881+
domain_id="domain1111111111111111111111111",
882+
parent_id="parentproject1111111111111111111",
883+
)
884+
885+
# Verify results
886+
assert result == Project(
887+
id="project1111111111111111111111111",
888+
name="ProjectOne",
889+
description="Project One description",
890+
is_enabled=True,
891+
domain_id="domain1111111111111111111111111",
892+
parent_id="parentproject1111111111111111111",
893+
)
894+
895+
# Verify mock calls
896+
mock_conn.identity.create_project.assert_called_once_with(
897+
name="ProjectOne",
898+
description="Project One description",
899+
is_enabled=True,
900+
domain_id="domain1111111111111111111111111",
901+
parent_id="parentproject1111111111111111111",
902+
)
903+
904+
def test_create_project_without_all_fields(
905+
self, mock_get_openstack_conn_identity
906+
):
907+
"""Test creating a identity project without all fields."""
908+
mock_conn = mock_get_openstack_conn_identity
909+
910+
mock_conn.identity.create_project.side_effect = (
911+
exceptions.BadRequestException(
912+
"Field required",
913+
)
914+
)
915+
916+
# Test create_project()
917+
identity_tools = self.get_identity_tools()
918+
919+
with pytest.raises(
920+
exceptions.BadRequestException,
921+
match="Field required",
922+
):
923+
identity_tools.create_project(
924+
name="ProjectOne",
925+
description="Project One description",
926+
is_enabled=True,
927+
domain_id=None,
928+
parent_id=None,
929+
)
930+
931+
# Verify mock calls
932+
mock_conn.identity.create_project.assert_called_once_with(
933+
name="ProjectOne",
934+
description="Project One description",
935+
is_enabled=True,
936+
domain_id=None,
937+
parent_id=None,
938+
)

0 commit comments

Comments
 (0)