Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions charms/garm/src/garm_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from garm_client.api.controller_info_api import ControllerInfoApi
from garm_client.api.credentials_api import CredentialsApi
from garm_client.api.first_run_api import FirstRunApi
from garm_client.api.instances_api import InstancesApi
from garm_client.api.login_api import LoginApi
from garm_client.api.organizations_api import OrganizationsApi
from garm_client.api.providers_api import ProvidersApi
Expand All @@ -29,6 +30,7 @@
from garm_client.models.create_scale_set_params import CreateScaleSetParams
from garm_client.models.create_template_params import CreateTemplateParams
from garm_client.models.forge_credentials import ForgeCredentials
from garm_client.models.instance import Instance
from garm_client.models.new_user_params import NewUserParams
from garm_client.models.organization import Organization
from garm_client.models.password_login_params import PasswordLoginParams
Expand Down Expand Up @@ -529,6 +531,48 @@ def list_scalesets(self) -> list[ScaleSet]:
except urllib3.exceptions.HTTPError as exc:
raise GarmConnectionError(f"GARM connection error: {exc}") from exc

def list_scale_set_instances(self, scaleset_id: int) -> list[Instance]:
Comment thread
yanksyoon marked this conversation as resolved.
"""List runner instances belonging to a scaleset."""
with self._api_client() as client:
try:
return (
InstancesApi(api_client=client).list_scale_set_instances(
scaleset_id=str(scaleset_id),
_request_timeout=_REQUEST_TIMEOUT,
)
or []
)
except ApiException as exc:
raise GarmApiError(
f"Failed to list instances for scaleset {scaleset_id} "
f"({exc.status}): {exc.body}"
) from exc
except urllib3.exceptions.HTTPError as exc:
raise GarmConnectionError(f"GARM connection error: {exc}") from exc

def delete_instance(
self,
instance_name: str,
*,
force_remove: bool = False,
bypass_gh_unauthorized: bool = False,
) -> None:
"""Request deletion of a GARM runner instance."""
with self._api_client() as client:
try:
InstancesApi(api_client=client).delete_instance(
instance_name=instance_name,
force_remove=force_remove,
bypass_gh_unauthorized=bypass_gh_unauthorized,
_request_timeout=_REQUEST_TIMEOUT,
)
except ApiException as exc:
raise GarmApiError(
f"Failed to delete runner {instance_name} ({exc.status}): {exc.body}"
) from exc
except urllib3.exceptions.HTTPError as exc:
raise GarmConnectionError(f"GARM connection error: {exc}") from exc

def find_org_id(self, org_name: str) -> str | None:
"""Find a GARM organization's UUID by name.

Expand Down
40 changes: 40 additions & 0 deletions charms/garm/tests/unit/test_garm_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,46 @@ def test_list_scalesets(api_response, expected_names):
assert [ss.name for ss in result] == expected_names


def test_list_scale_set_instances_converts_id_to_string():
"""
arrange: An authenticated client and a generated InstancesApi response.
act: List instances for scaleset ID 42.
assert: The wrapper converts the integer ID to the generated client's required string.
"""
client = GarmAuthenticatedClient(BASE_URL, "token")
instance = Instance(name="runner-1", status="running", scale_set_id=42)
with _stub_api_client(client):
with patch("garm_api.InstancesApi") as MockApi:
MockApi.return_value.list_scale_set_instances.return_value = [instance]
result = client.list_scale_set_instances(42)

MockApi.return_value.list_scale_set_instances.assert_called_once_with(
scaleset_id="42", _request_timeout=30
)
assert result == [instance]


def test_delete_instance_passes_force_and_bypass_flags():
"""
arrange: An authenticated client and a generated InstancesApi stub.
act: Delete a runner with both cleanup flags enabled.
assert: The wrapper forwards the runner name, flags, and request timeout.
"""
client = GarmAuthenticatedClient(BASE_URL, "token")
with _stub_api_client(client):
with patch("garm_api.InstancesApi") as MockApi:
client.delete_instance(
"runner-1", force_remove=True, bypass_gh_unauthorized=True
)

MockApi.return_value.delete_instance.assert_called_once_with(
instance_name="runner-1",
force_remove=True,
bypass_gh_unauthorized=True,
_request_timeout=30,
)


@pytest.mark.parametrize(
"target, registered, expected",
[
Expand Down
Loading