Skip to content

Commit a34aa34

Browse files
jja6312halucinor
authored andcommitted
feat: get images tool (#53)
* feat: add get images (#20) * docs: change get_images docstring to sphinx style * refactor: restructure get images testing for better maintainability
1 parent 755a14d commit a34aa34

File tree

2 files changed

+131
-63
lines changed

2 files changed

+131
-63
lines changed

src/openstack_mcp_server/tools/image_tools.py

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,43 @@ def register_tools(self, mcp: FastMCP):
1616
Register Image-related tools with the FastMCP instance.
1717
"""
1818

19-
mcp.tool()(self.get_image_images)
19+
mcp.tool()(self.get_images)
2020
mcp.tool()(self.create_image)
2121

22-
def get_image_images(self) -> str:
22+
def get_images(
23+
self,
24+
name: str | None = None,
25+
status: str | None = None,
26+
visibility: str | None = None,
27+
) -> list[Image]:
2328
"""
24-
Get the list of Image images by invoking the registered tool.
29+
Get the list of OpenStack images with optional filtering.
2530
26-
:return: A string containing the names, IDs, and statuses of the images.
31+
The filtering behavior is as follows:
32+
- By default, all available images are returned without any filtering applied.
33+
- Filters are only applied when specific values are provided by the user.
34+
35+
:param name: Filter by image name
36+
:param status: Filter by status
37+
:param visibility: Filter by visibility
38+
:return: A list of Image objects.
2739
"""
28-
# Initialize connection
2940
conn = get_openstack_conn()
3041

31-
# List the servers
42+
# Build filters for the image query
43+
filters = {}
44+
if name and name.strip():
45+
filters["name"] = name.strip()
46+
if status and status.strip():
47+
filters["status"] = status.strip()
48+
if visibility and visibility.strip():
49+
filters["visibility"] = visibility.strip()
50+
3251
image_list = []
33-
for image in conn.image.images():
34-
image_list.append(
35-
f"{image.name} ({image.id}) - Status: {image.status}",
36-
)
52+
for image in conn.image.images(**filters):
53+
image_list.append(Image(**image))
3754

38-
return "\n".join(image_list)
55+
return image_list
3956

4057
def create_image(self, image_data: CreateImage) -> Image:
4158
"""Create a new Openstack image.

tests/tools/test_image_tools.py

Lines changed: 103 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -46,80 +46,131 @@ def image_factory(**overrides):
4646

4747
return defaults
4848

49-
def test_get_image_images_success(self, mock_get_openstack_conn_image):
49+
def test_get_images_success(self, mock_get_openstack_conn_image):
5050
"""Test getting image images successfully."""
5151
mock_conn = mock_get_openstack_conn_image
52-
53-
# Create mock image objects
54-
mock_image1 = Mock()
55-
mock_image1.name = "ubuntu-20.04-server"
56-
mock_image1.id = "img-123-abc-def"
57-
mock_image1.status = "active"
58-
59-
mock_image2 = Mock()
60-
mock_image2.name = "centos-8-stream"
61-
mock_image2.id = "img-456-ghi-jkl"
62-
mock_image2.status = "active"
63-
64-
# Configure mock image.images()
52+
mock_image1 = self.image_factory(
53+
id="img-123-abc-def",
54+
name="ubuntu-20.04-server",
55+
status="active",
56+
visibility="public",
57+
checksum="abc123",
58+
size=1073741824,
59+
)
60+
mock_image2 = self.image_factory(
61+
id="img-456-ghi-jkl",
62+
name="centos-8-stream",
63+
status="active",
64+
visibility="public",
65+
checksum="def456",
66+
size=2147483648,
67+
)
6568
mock_conn.image.images.return_value = [mock_image1, mock_image2]
6669

67-
# Test ImageTools
68-
image_tools = ImageTools()
69-
result = image_tools.get_image_images()
70+
result = ImageTools().get_images()
7071

71-
# Verify results
72-
expected_output = (
73-
"ubuntu-20.04-server (img-123-abc-def) - Status: active\n"
74-
"centos-8-stream (img-456-ghi-jkl) - Status: active"
75-
)
76-
assert result == expected_output
77-
78-
# Verify mock calls
7972
mock_conn.image.images.assert_called_once()
73+
expected_output = [
74+
Image(**mock_image1),
75+
Image(**mock_image2),
76+
]
77+
assert result == expected_output
8078

81-
def test_get_image_images_empty_list(self, mock_get_openstack_conn_image):
79+
def test_get_images_empty_list(self, mock_get_openstack_conn_image):
8280
"""Test getting image images when no images exist."""
8381
mock_conn = mock_get_openstack_conn_image
84-
85-
# Empty image list
8682
mock_conn.image.images.return_value = []
8783

88-
image_tools = ImageTools()
89-
result = image_tools.get_image_images()
90-
91-
# Verify empty string
92-
assert result == ""
84+
result = ImageTools().get_images()
9385

9486
mock_conn.image.images.assert_called_once()
87+
assert result == []
9588

96-
def test_get_image_images_with_empty_name(
97-
self,
98-
mock_get_openstack_conn_image,
89+
def test_get_images_with_status_filter(
90+
self, mock_get_openstack_conn_image
9991
):
100-
"""Test images with empty or None names."""
92+
"""Test getting images with status filter."""
10193
mock_conn = mock_get_openstack_conn_image
94+
mock_image = self.image_factory(
95+
id="img-123-abc-def",
96+
name="ubuntu-20.04-server",
97+
status="active",
98+
visibility="public",
99+
checksum="abc123",
100+
size=1073741824,
101+
)
102+
mock_conn.image.images.return_value = [mock_image]
102103

103-
# Images with empty name (edge case)
104-
mock_image1 = Mock()
105-
mock_image1.name = "normal-image"
106-
mock_image1.id = "img-normal"
107-
mock_image1.status = "active"
104+
result = ImageTools().get_images(status="active")
108105

109-
mock_image2 = Mock()
110-
mock_image2.name = "" # Empty name
111-
mock_image2.id = "img-empty-name"
112-
mock_image2.status = "active"
106+
mock_conn.image.images.assert_called_once_with(status="active")
107+
expected_output = [Image(**mock_image)]
108+
assert result == expected_output
113109

114-
mock_conn.image.images.return_value = [mock_image1, mock_image2]
110+
def test_get_images_with_visibility_filter(
111+
self, mock_get_openstack_conn_image
112+
):
113+
"""Test getting images with visibility filter."""
114+
mock_conn = mock_get_openstack_conn_image
115+
mock_image = self.image_factory(
116+
id="img-456-ghi-jkl",
117+
name="centos-8-stream",
118+
status="queued",
119+
visibility="private",
120+
checksum="def456",
121+
size=2147483648,
122+
)
123+
mock_conn.image.images.return_value = [mock_image]
115124

116-
image_tools = ImageTools()
117-
result = image_tools.get_image_images()
125+
result = ImageTools().get_images(visibility="private")
118126

119-
assert "normal-image (img-normal) - Status: active" in result
120-
assert " (img-empty-name) - Status: active" in result # Empty name
127+
mock_conn.image.images.assert_called_once_with(visibility="private")
128+
expected_output = [Image(**mock_image)]
129+
assert result == expected_output
121130

122-
mock_conn.image.images.assert_called_once()
131+
def test_get_images_with_name_filter(self, mock_get_openstack_conn_image):
132+
"""Test getting images with name filter."""
133+
mock_conn = mock_get_openstack_conn_image
134+
mock_image = self.image_factory(
135+
id="img-789-mno-pqr",
136+
name="centos-8-stream",
137+
status="active",
138+
visibility="public",
139+
checksum="ghi789",
140+
size=3221225472,
141+
)
142+
mock_conn.image.images.return_value = [mock_image]
143+
144+
result = ImageTools().get_images(name="centos-8-stream")
145+
146+
mock_conn.image.images.assert_called_once_with(name="centos-8-stream")
147+
expected_output = [Image(**mock_image)]
148+
assert result == expected_output
149+
150+
def test_get_images_with_multiple_filters(
151+
self, mock_get_openstack_conn_image
152+
):
153+
"""Test getting images with multiple filters."""
154+
mock_conn = mock_get_openstack_conn_image
155+
mock_image = self.image_factory(
156+
id="img-multi-filter",
157+
name="ubuntu-20.04-server",
158+
status="active",
159+
visibility="public",
160+
checksum="multi123",
161+
size=1073741824,
162+
)
163+
mock_conn.image.images.return_value = [mock_image]
164+
165+
result = ImageTools().get_images(
166+
name="ubuntu-20.04-server", status="active", visibility="public"
167+
)
168+
169+
mock_conn.image.images.assert_called_once_with(
170+
name="ubuntu-20.04-server", status="active", visibility="public"
171+
)
172+
expected_output = [Image(**mock_image)]
173+
assert result == expected_output
123174

124175
def test_create_image_success_with_volume_id(
125176
self,

0 commit comments

Comments
 (0)