diff --git a/nuon/api/installs/refresh_install_health_cluster_access.py b/nuon/api/installs/refresh_install_health_cluster_access.py new file mode 100644 index 00000000..ebbc6b2f --- /dev/null +++ b/nuon/api/installs/refresh_install_health_cluster_access.py @@ -0,0 +1,227 @@ +from http import HTTPStatus +from typing import Any +from urllib.parse import quote + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.service_refresh_install_health_cluster_access_request import ( + ServiceRefreshInstallHealthClusterAccessRequest, +) +from ...models.service_refresh_install_health_cluster_access_response import ( + ServiceRefreshInstallHealthClusterAccessResponse, +) +from ...models.stderr_err_response import StderrErrResponse +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + install_id: str, + *, + body: ServiceRefreshInstallHealthClusterAccessRequest | Unset = UNSET, +) -> dict[str, Any]: + headers: dict[str, Any] = {} + + _kwargs: dict[str, Any] = { + "method": "post", + "url": "/v1/installs/{install_id}/health/cluster-access".format( + install_id=quote(str(install_id), safe=""), + ), + } + + if not isinstance(body, Unset): + _kwargs["json"] = body.to_dict() + + headers["Content-Type"] = "application/json" + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> ServiceRefreshInstallHealthClusterAccessResponse | StderrErrResponse | None: + if response.status_code == 200: + response_200 = ServiceRefreshInstallHealthClusterAccessResponse.from_dict(response.json()) + + return response_200 + + if response.status_code == 400: + response_400 = StderrErrResponse.from_dict(response.json()) + + return response_400 + + if response.status_code == 401: + response_401 = StderrErrResponse.from_dict(response.json()) + + return response_401 + + if response.status_code == 403: + response_403 = StderrErrResponse.from_dict(response.json()) + + return response_403 + + if response.status_code == 404: + response_404 = StderrErrResponse.from_dict(response.json()) + + return response_404 + + if response.status_code == 500: + response_500 = StderrErrResponse.from_dict(response.json()) + + return response_500 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[ServiceRefreshInstallHealthClusterAccessResponse | StderrErrResponse]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + install_id: str, + *, + client: AuthenticatedClient, + body: ServiceRefreshInstallHealthClusterAccessRequest | Unset = UNSET, +) -> Response[ServiceRefreshInstallHealthClusterAccessResponse | StderrErrResponse]: + """refresh the cluster access component health reads through + + Derives the install's cluster access from its current stack outputs and the chosen role, then stores + it for the runner's health engine. Use when health reports unknown because the install has not been + deployed since component health was enabled, or after the cluster's endpoint or role changed. The + runner picks the refreshed access up within a minute. Requires the component-health feature. + + Args: + install_id (str): + body (ServiceRefreshInstallHealthClusterAccessRequest | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[ServiceRefreshInstallHealthClusterAccessResponse | StderrErrResponse] + """ + + kwargs = _get_kwargs( + install_id=install_id, + body=body, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + install_id: str, + *, + client: AuthenticatedClient, + body: ServiceRefreshInstallHealthClusterAccessRequest | Unset = UNSET, +) -> ServiceRefreshInstallHealthClusterAccessResponse | StderrErrResponse | None: + """refresh the cluster access component health reads through + + Derives the install's cluster access from its current stack outputs and the chosen role, then stores + it for the runner's health engine. Use when health reports unknown because the install has not been + deployed since component health was enabled, or after the cluster's endpoint or role changed. The + runner picks the refreshed access up within a minute. Requires the component-health feature. + + Args: + install_id (str): + body (ServiceRefreshInstallHealthClusterAccessRequest | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + ServiceRefreshInstallHealthClusterAccessResponse | StderrErrResponse + """ + + return sync_detailed( + install_id=install_id, + client=client, + body=body, + ).parsed + + +async def asyncio_detailed( + install_id: str, + *, + client: AuthenticatedClient, + body: ServiceRefreshInstallHealthClusterAccessRequest | Unset = UNSET, +) -> Response[ServiceRefreshInstallHealthClusterAccessResponse | StderrErrResponse]: + """refresh the cluster access component health reads through + + Derives the install's cluster access from its current stack outputs and the chosen role, then stores + it for the runner's health engine. Use when health reports unknown because the install has not been + deployed since component health was enabled, or after the cluster's endpoint or role changed. The + runner picks the refreshed access up within a minute. Requires the component-health feature. + + Args: + install_id (str): + body (ServiceRefreshInstallHealthClusterAccessRequest | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[ServiceRefreshInstallHealthClusterAccessResponse | StderrErrResponse] + """ + + kwargs = _get_kwargs( + install_id=install_id, + body=body, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + install_id: str, + *, + client: AuthenticatedClient, + body: ServiceRefreshInstallHealthClusterAccessRequest | Unset = UNSET, +) -> ServiceRefreshInstallHealthClusterAccessResponse | StderrErrResponse | None: + """refresh the cluster access component health reads through + + Derives the install's cluster access from its current stack outputs and the chosen role, then stores + it for the runner's health engine. Use when health reports unknown because the install has not been + deployed since component health was enabled, or after the cluster's endpoint or role changed. The + runner picks the refreshed access up within a minute. Requires the component-health feature. + + Args: + install_id (str): + body (ServiceRefreshInstallHealthClusterAccessRequest | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + ServiceRefreshInstallHealthClusterAccessResponse | StderrErrResponse + """ + + return ( + await asyncio_detailed( + install_id=install_id, + client=client, + body=body, + ) + ).parsed diff --git a/nuon/models/__init__.py b/nuon/models/__init__.py index 78fd5410..072c503d 100644 --- a/nuon/models/__init__.py +++ b/nuon/models/__init__.py @@ -674,6 +674,8 @@ ServicePutInstallComponentHealthCheckRequestDetails, ) from .service_readme import ServiceReadme +from .service_refresh_install_health_cluster_access_request import ServiceRefreshInstallHealthClusterAccessRequest +from .service_refresh_install_health_cluster_access_response import ServiceRefreshInstallHealthClusterAccessResponse from .service_remove_action_labels_request import ServiceRemoveActionLabelsRequest from .service_remove_component_labels_request import ServiceRemoveComponentLabelsRequest from .service_remove_install_labels_request import ServiceRemoveInstallLabelsRequest @@ -1424,6 +1426,8 @@ "ServicePutInstallComponentHealthCheckRequest", "ServicePutInstallComponentHealthCheckRequestDetails", "ServiceReadme", + "ServiceRefreshInstallHealthClusterAccessRequest", + "ServiceRefreshInstallHealthClusterAccessResponse", "ServiceRemoveActionLabelsRequest", "ServiceRemoveComponentLabelsRequest", "ServiceRemoveInstallLabelsRequest", diff --git a/nuon/models/app_install.py b/nuon/models/app_install.py index eacb6511..f89b9aa9 100644 --- a/nuon/models/app_install.py +++ b/nuon/models/app_install.py @@ -77,6 +77,9 @@ class AppInstall: expected_project_id (str | Unset): expected_subscription_id (str | Unset): gcp_account (AppGCPAccount | Unset): + health_cluster_error (str | Unset): HealthClusterError is why component health cannot currently inspect the + install's cluster, empty when it can. Install-level because it is one + fact about the install rather than a property of any component. id (str | Unset): install_action_workflows (list[AppInstallActionWorkflow] | Unset): install_components (list[AppInstallComponent] | Unset): @@ -90,6 +93,9 @@ class AppInstall: install_stack (AppInstallStack | Unset): install_states (list[AppInstallState] | Unset): labels (GithubComNuoncoNuonPkgLabelsLabels | Unset): + last_health_report_at (str | Unset): LastHealthReportAt is when a runner last reported component health. It is + how the staleness sweep finds installs that went quiet without polling + every install individually. lifecycle_phase (AppInstallLifecyclePhase | Unset): links (AppInstallLinks | Unset): metadata (AppInstallMetadata | Unset): @@ -138,6 +144,7 @@ class AppInstall: expected_project_id: str | Unset = UNSET expected_subscription_id: str | Unset = UNSET gcp_account: AppGCPAccount | Unset = UNSET + health_cluster_error: str | Unset = UNSET id: str | Unset = UNSET install_action_workflows: list[AppInstallActionWorkflow] | Unset = UNSET install_components: list[AppInstallComponent] | Unset = UNSET @@ -150,6 +157,7 @@ class AppInstall: install_stack: AppInstallStack | Unset = UNSET install_states: list[AppInstallState] | Unset = UNSET labels: GithubComNuoncoNuonPkgLabelsLabels | Unset = UNSET + last_health_report_at: str | Unset = UNSET lifecycle_phase: AppInstallLifecyclePhase | Unset = UNSET links: AppInstallLinks | Unset = UNSET metadata: AppInstallMetadata | Unset = UNSET @@ -247,6 +255,8 @@ def to_dict(self) -> dict[str, Any]: if not isinstance(self.gcp_account, Unset): gcp_account = self.gcp_account.to_dict() + health_cluster_error = self.health_cluster_error + id = self.id install_action_workflows: list[dict[str, Any]] | Unset = UNSET @@ -312,6 +322,8 @@ def to_dict(self) -> dict[str, Any]: if not isinstance(self.labels, Unset): labels = self.labels.to_dict() + last_health_report_at = self.last_health_report_at + lifecycle_phase: dict[str, Any] | Unset = UNSET if not isinstance(self.lifecycle_phase, Unset): lifecycle_phase = self.lifecycle_phase.to_dict() @@ -419,6 +431,8 @@ def to_dict(self) -> dict[str, Any]: field_dict["expected_subscription_id"] = expected_subscription_id if gcp_account is not UNSET: field_dict["gcp_account"] = gcp_account + if health_cluster_error is not UNSET: + field_dict["health_cluster_error"] = health_cluster_error if id is not UNSET: field_dict["id"] = id if install_action_workflows is not UNSET: @@ -443,6 +457,8 @@ def to_dict(self) -> dict[str, Any]: field_dict["install_states"] = install_states if labels is not UNSET: field_dict["labels"] = labels + if last_health_report_at is not UNSET: + field_dict["last_health_report_at"] = last_health_report_at if lifecycle_phase is not UNSET: field_dict["lifecycle_phase"] = lifecycle_phase if links is not UNSET: @@ -621,6 +637,8 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: else: gcp_account = AppGCPAccount.from_dict(_gcp_account) + health_cluster_error = d.pop("health_cluster_error", UNSET) + id = d.pop("id", UNSET) _install_action_workflows = d.pop("install_action_workflows", UNSET) @@ -709,6 +727,8 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: else: labels = GithubComNuoncoNuonPkgLabelsLabels.from_dict(_labels) + last_health_report_at = d.pop("last_health_report_at", UNSET) + _lifecycle_phase = d.pop("lifecycle_phase", UNSET) lifecycle_phase: AppInstallLifecyclePhase | Unset if isinstance(_lifecycle_phase, Unset): @@ -809,6 +829,7 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: expected_project_id=expected_project_id, expected_subscription_id=expected_subscription_id, gcp_account=gcp_account, + health_cluster_error=health_cluster_error, id=id, install_action_workflows=install_action_workflows, install_components=install_components, @@ -821,6 +842,7 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: install_stack=install_stack, install_states=install_states, labels=labels, + last_health_report_at=last_health_report_at, lifecycle_phase=lifecycle_phase, links=links, metadata=metadata, diff --git a/nuon/models/service_install_component_health_summary.py b/nuon/models/service_install_component_health_summary.py index 726ed8e7..136d99c6 100644 --- a/nuon/models/service_install_component_health_summary.py +++ b/nuon/models/service_install_component_health_summary.py @@ -20,6 +20,8 @@ class ServiceInstallComponentHealthSummary: component_name (str | Unset): current_health (str | Unset): install_component_id (str | Unset): + observed_seconds (int | Unset): ObservedSeconds distinguishes "no data" from "0% up" — without it a + component that was never observed renders as total downtime. uptime_percent (float | Unset): """ @@ -27,6 +29,7 @@ class ServiceInstallComponentHealthSummary: component_name: str | Unset = UNSET current_health: str | Unset = UNSET install_component_id: str | Unset = UNSET + observed_seconds: int | Unset = UNSET uptime_percent: float | Unset = UNSET additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) @@ -39,6 +42,8 @@ def to_dict(self) -> dict[str, Any]: install_component_id = self.install_component_id + observed_seconds = self.observed_seconds + uptime_percent = self.uptime_percent field_dict: dict[str, Any] = {} @@ -52,6 +57,8 @@ def to_dict(self) -> dict[str, Any]: field_dict["current_health"] = current_health if install_component_id is not UNSET: field_dict["install_component_id"] = install_component_id + if observed_seconds is not UNSET: + field_dict["observed_seconds"] = observed_seconds if uptime_percent is not UNSET: field_dict["uptime_percent"] = uptime_percent @@ -68,6 +75,8 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: install_component_id = d.pop("install_component_id", UNSET) + observed_seconds = d.pop("observed_seconds", UNSET) + uptime_percent = d.pop("uptime_percent", UNSET) service_install_component_health_summary = cls( @@ -75,6 +84,7 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: component_name=component_name, current_health=current_health, install_component_id=install_component_id, + observed_seconds=observed_seconds, uptime_percent=uptime_percent, ) diff --git a/nuon/models/service_install_health_timeline_response.py b/nuon/models/service_install_health_timeline_response.py index 32628a8e..38a5bef7 100644 --- a/nuon/models/service_install_health_timeline_response.py +++ b/nuon/models/service_install_health_timeline_response.py @@ -20,6 +20,8 @@ class ServiceInstallHealthTimelineResponse: """ Attributes: + cluster_access_error (str | Unset): ClusterAccessError is why health cannot currently inspect the install's + cluster, empty when it can. Surfaced once here rather than per component. components (list[ServiceInstallComponentHealthSummary] | Unset): current_health (str | Unset): daily (list[ServiceDailyHealthBucket] | Unset): @@ -29,6 +31,7 @@ class ServiceInstallHealthTimelineResponse: uptime_percent (float | Unset): """ + cluster_access_error: str | Unset = UNSET components: list[ServiceInstallComponentHealthSummary] | Unset = UNSET current_health: str | Unset = UNSET daily: list[ServiceDailyHealthBucket] | Unset = UNSET @@ -39,6 +42,8 @@ class ServiceInstallHealthTimelineResponse: additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: + cluster_access_error = self.cluster_access_error + components: list[dict[str, Any]] | Unset = UNSET if not isinstance(self.components, Unset): components = [] @@ -66,6 +71,8 @@ def to_dict(self) -> dict[str, Any]: field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update({}) + if cluster_access_error is not UNSET: + field_dict["cluster_access_error"] = cluster_access_error if components is not UNSET: field_dict["components"] = components if current_health is not UNSET: @@ -89,6 +96,8 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: from ..models.service_install_component_health_summary import ServiceInstallComponentHealthSummary d = dict(src_dict) + cluster_access_error = d.pop("cluster_access_error", UNSET) + _components = d.pop("components", UNSET) components: list[ServiceInstallComponentHealthSummary] | Unset = UNSET if _components is not UNSET: @@ -118,6 +127,7 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: uptime_percent = d.pop("uptime_percent", UNSET) service_install_health_timeline_response = cls( + cluster_access_error=cluster_access_error, components=components, current_health=current_health, daily=daily, diff --git a/nuon/models/service_refresh_install_health_cluster_access_request.py b/nuon/models/service_refresh_install_health_cluster_access_request.py new file mode 100644 index 00000000..a4a0adc6 --- /dev/null +++ b/nuon/models/service_refresh_install_health_cluster_access_request.py @@ -0,0 +1,62 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="ServiceRefreshInstallHealthClusterAccessRequest") + + +@_attrs_define +class ServiceRefreshInstallHealthClusterAccessRequest: + """ + Attributes: + role_name (str | Unset): RoleName is the identity health should read the cluster through. Empty + means the maintenance role, the same default drift and action runs use. + """ + + role_name: str | Unset = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + role_name = self.role_name + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if role_name is not UNSET: + field_dict["role_name"] = role_name + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + role_name = d.pop("role_name", UNSET) + + service_refresh_install_health_cluster_access_request = cls( + role_name=role_name, + ) + + service_refresh_install_health_cluster_access_request.additional_properties = d + return service_refresh_install_health_cluster_access_request + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/nuon/models/service_refresh_install_health_cluster_access_response.py b/nuon/models/service_refresh_install_health_cluster_access_response.py new file mode 100644 index 00000000..51f2447b --- /dev/null +++ b/nuon/models/service_refresh_install_health_cluster_access_response.py @@ -0,0 +1,79 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="ServiceRefreshInstallHealthClusterAccessResponse") + + +@_attrs_define +class ServiceRefreshInstallHealthClusterAccessResponse: + """ + Attributes: + cluster_found (bool | Unset): + cluster_id (str | Unset): + role_name (str | Unset): + """ + + cluster_found: bool | Unset = UNSET + cluster_id: str | Unset = UNSET + role_name: str | Unset = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + cluster_found = self.cluster_found + + cluster_id = self.cluster_id + + role_name = self.role_name + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if cluster_found is not UNSET: + field_dict["cluster_found"] = cluster_found + if cluster_id is not UNSET: + field_dict["cluster_id"] = cluster_id + if role_name is not UNSET: + field_dict["role_name"] = role_name + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + cluster_found = d.pop("cluster_found", UNSET) + + cluster_id = d.pop("cluster_id", UNSET) + + role_name = d.pop("role_name", UNSET) + + service_refresh_install_health_cluster_access_response = cls( + cluster_found=cluster_found, + cluster_id=cluster_id, + role_name=role_name, + ) + + service_refresh_install_health_cluster_access_response.additional_properties = d + return service_refresh_install_health_cluster_access_response + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/pyproject.toml b/pyproject.toml index d6c6c0bd..6129f6d2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "nuon" -version = "0.19.1096" +version = "0.19.1098" description = "A client library for accessing Nuon" authors = [] requires-python = ">=3.11" diff --git a/version.txt b/version.txt index 681fe830..b5fbfcc7 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.19.1096 +0.19.1098