From 66ae01345ab696115b13293cb9a6eb316fd57abb Mon Sep 17 00:00:00 2001 From: Tobia Zanarella Date: Fri, 30 Oct 2026 11:39:17 +0100 Subject: [PATCH 1/3] Regenerate from the specification with the hosting SSL checks and the discounted renew price - New operation canInstallHostingSslCertificate (GET .../ssl-certificates/can-install) with its models and the HostingSslCertificateCanInstallReason enum. - Purchase.renew_price_discounted. - HostingSslCertificateInstallRequest: destination_type without default (not sent unless set), cn_type defaults to WILDCARD. - CHANGELOG for 1.1.0. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 34 ++ spec/openapi.yaml | 249 ++++++++++++++- .../can_install_hosting_ssl_certificate.py | 299 ++++++++++++++++++ .../install_hosting_ssl_certificate.py | 44 ++- src/shellrent_sdk/client/models/__init__.py | 14 + ...install_hosting_ssl_certificate_cn_type.py | 9 + ...osting_ssl_certificate_destination_type.py | 10 + .../hosting_ssl_certificate_can_install.py | 149 +++++++++ ...ting_ssl_certificate_can_install_reason.py | 12 + ...ng_ssl_certificate_can_install_response.py | 99 ++++++ ...hosting_ssl_certificate_install_request.py | 25 +- .../models/hosting_ssl_dns_delegation.py | 97 ++++++ .../client/models/o_auth_error.py | 3 +- src/shellrent_sdk/client/models/purchase.py | 26 ++ 14 files changed, 1036 insertions(+), 34 deletions(-) create mode 100644 src/shellrent_sdk/client/api/hosting/can_install_hosting_ssl_certificate.py create mode 100644 src/shellrent_sdk/client/models/can_install_hosting_ssl_certificate_cn_type.py create mode 100644 src/shellrent_sdk/client/models/can_install_hosting_ssl_certificate_destination_type.py create mode 100644 src/shellrent_sdk/client/models/hosting_ssl_certificate_can_install.py create mode 100644 src/shellrent_sdk/client/models/hosting_ssl_certificate_can_install_reason.py create mode 100644 src/shellrent_sdk/client/models/hosting_ssl_certificate_can_install_response.py create mode 100644 src/shellrent_sdk/client/models/hosting_ssl_dns_delegation.py diff --git a/CHANGELOG.md b/CHANGELOG.md index f87dfd7..0a7b6d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.1.0] - 2026-10-30 + +Regenerated from the Shellrent API specification with the new SSL checks of the hosting and the discounted +renew price of the purchases. No breaking changes in names or signatures. + +### Added + +- `can_install_hosting_ssl_certificate` (`hosting`, `GET /api/v3/hostings/{hosting_id}/ssl-certificates/can-install`): + whether an SSL certificate can be installed on the hosting now, with the same fields of the installation as + keyword-only query parameters, and why not (`reason`). For a Let's Encrypt certificate, `dns_delegation` has the + CNAME record that delegates its DNS validation, which must be created when the DNS are not on the Shellrent + nameservers. +- `HostingSslCertificateCanInstall`, `HostingSslCertificateCanInstallResponse`, `HostingSslDnsDelegation`, the enum + `HostingSslCertificateCanInstallReason` (`ALREADY_INSTALLED`, `CNAME_MISSING`, `LIMIT_REACHED`, + `MAIL_NOT_SUPPORTED`, `OPERATION_RUNNING`) and the enums of the query parameters + `CanInstallHostingSslCertificateDestinationType` and `CanInstallHostingSslCertificateCnType`. +- `Purchase.renew_price_discounted` (`Amount | None`): the renew price with the discount of the discount promotion + applied to the purchase; `None` without a discount and for sub-accounts. + +### Changed + +- `HostingSslCertificateInstallRequest.destination_type` defaults to `UNSET` instead of `web`, and is not sent + unless set: the API then installs on the website and the mail of a main hosting (`all`), or on the website of a + subdomain (`web`). `cn_type` defaults to `WILDCARD` instead of `NORMAL`, and is ignored with + `ssl_certificate_id`. +- `install_hosting_ssl_certificate` follows the rules of the control panel. A Let's Encrypt certificate with DNS + that are not on the Shellrent nameservers requires the CNAME record of `dns_delegation` (409 without it). + `ssl_certificate_id` accepts only the active certificates of the owner of the hosting (404 otherwise), and on the + mail servers only wildcard certificates or certificates with SAN (400). A certificate already installed, the limit + of Let's Encrypt installations or an operation running respond 409 instead of 400, and a mail server that does + not accept certificates responds 403. +- Descriptions of the OAuth2 errors updated with `unauthorized_client` (owner of the client without two-factor + authentication). + ## [1.0.0] - 2026-10-16 Functions and models regenerated from the updated specification of the Shellrent API, which renames diff --git a/spec/openapi.yaml b/spec/openapi.yaml index e4210be..b409afe 100644 --- a/spec/openapi.yaml +++ b/spec/openapi.yaml @@ -3124,8 +3124,16 @@ paths: - Hosting summary: Install SSL certificate on hosting description: >- - Install an SSL certificate on hosting or create and install Let's - Encrypt + Installs a purchased SSL certificate on the hosting, or issues and + installs a Let's Encrypt certificate, with the rules of the control + panel. With DNS that are not on our nameservers, Let's Encrypt requires + the CNAME record that delegates its DNS validation: `GET + /api/v3/hostings/{hosting_id}/ssl-certificates/can-install` tells + whether the installation can start, and which record to create. 409 when + a certificate is already installed on the destination, the CNAME record + is missing, the limit of Let's Encrypt installations is reached or an + operation is running; 403 when the mail server of the hosting does not + accept certificates operationId: installHostingSslCertificate parameters: - $ref: '#/components/parameters/HostingId' @@ -3244,6 +3252,83 @@ paths: security: - oauth2: - hosting:write + /api/v3/hostings/{hosting_id}/ssl-certificates/can-install: + get: + tags: + - Hosting + summary: Can install SSL certificate on hosting + description: >- + Checks, with the rules of the installation (`POST + /api/v3/hostings/{hosting_id}/ssl-certificates`) and its fields in the + query, whether the SSL certificate can be installed on the hosting now, + and why not: a certificate already installed, the CNAME record that + delegates the validation of Let's Encrypt missing from DNS that are not + on our nameservers (`dns_delegation` has the record to create), the + limit of Let's Encrypt installations, an operation running. With DNS on + our nameservers the installation creates the CNAME record by itself. + Invalid fields respond 400, as in the installation + operationId: canInstallHostingSslCertificate + parameters: + - $ref: '#/components/parameters/HostingId' + - name: ssl_certificate_id + in: query + description: >- + Id of the purchased SSL certificate to install (`GET + /api/v3/ssl-certificates/{ssl_certificate_id}`); omit it for a Let's + Encrypt certificate + required: false + schema: + type: + - integer + - 'null' + - name: destination_type + in: query + description: >- + Where to install the certificate: `web` for the website, `mail` for + the mail servers, `all` for both. When omitted, `all` on a main + hosting and `web` on a subdomain, where only `web` is allowed + required: false + schema: + type: string + enum: + - all + - web + - mail + - name: cn_type + in: query + description: >- + Type of the Let's Encrypt certificate: `NORMAL`, or `WILDCARD` for a + wildcard certificate. Omit this parameter if you're sending + `ssl_certificate_id`. + required: false + schema: + type: string + default: WILDCARD + enum: + - NORMAL + - WILDCARD + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/HostingSslCertificateCanInstallResponse' + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + '429': + $ref: '#/components/responses/TooManyRequests' + '500': + $ref: '#/components/responses/ServerError' + security: + - oauth2: + - hosting:read /api/v3/hostings/{hosting_id}/backups: get: tags: @@ -12263,7 +12348,10 @@ paths: schema: $ref: '#/components/schemas/OAuthTokenResponse' '400': - description: Invalid request + description: >- + Invalid request, or `unauthorized_client`: the client is + authenticated, but its owner has not enabled two-factor + authentication content: application/json: schema: @@ -12442,8 +12530,8 @@ components: properties: error: description: >- - OAuth2 error code, for example `invalid_client`, `invalid_scope` or - `rate_limited` + OAuth2 error code, for example `invalid_client`, `invalid_scope`, + `unauthorized_client` or `rate_limited` type: string example: invalid_client error_description: @@ -12525,6 +12613,18 @@ components: - api-public - api-internal example: api-public + HostingSslCertificateCanInstallReason: + description: >- + Why an SSL certificate cannot be installed on the hosting now (`GET + /api/v3/hostings/{hosting_id}/ssl-certificates/can-install`) + type: string + enum: + - ALREADY_INSTALLED + - CNAME_MISSING + - LIMIT_REACHED + - MAIL_NOT_SUPPORTED + - OPERATION_RUNNING + example: CNAME_MISSING BuyPrepaidCreditTopupRequest: required: - amount @@ -14216,25 +14316,30 @@ components: additionalProperties: false HostingSslCertificateInstallRequest: description: >- - SSL certificate to install on the hosting; with no body, a Let's Encrypt - certificate for the website + SSL certificate to install on the hosting; with no body, a wildcard + Let's Encrypt certificate for the website and the mail of a main + hosting, or for the website of a subdomain properties: ssl_certificate_id: description: >- Id of a purchased SSL certificate to install (`GET - /api/v3/ssl-certificates/{ssl_certificate_id}`); omit it, or send - null, to install a Let's Encrypt certificate, issued if the hosting - has none + /api/v3/ssl-certificates/{ssl_certificate_id}`): an active + certificate of the owner of the hosting, a wildcard one or one with + SAN to install it also on the mail servers. Omit it, or send null, + to install a Let's Encrypt certificate, issued if the hosting has + none: when the DNS of the domain are not on our nameservers, the + issue requires the CNAME record that delegates the DNS validation + (`GET /api/v3/hostings/{hosting_id}/ssl-certificates/can-install`) type: - integer - 'null' destination_type: description: >- Where to install the certificate: `web` for the website, `mail` for - the mail servers, `all` for both. `mail` and `all` require `cn_type` - `WILDCARD`; on a subdomain only `web` is allowed + the mail servers, `all` for both. When omitted, `all` on a main + hosting and `web` on a subdomain, where only `web` is allowed. + `mail` and `all` require a Let's Encrypt `cn_type` `WILDCARD` type: string - default: web enum: - all - web @@ -14243,9 +14348,10 @@ components: description: >- Type of the certificate: `NORMAL`, or `WILDCARD` for a wildcard certificate. Without `ssl_certificate_id` it chooses the Let's - Encrypt certificate to install + Encrypt certificate to install. Omit this field if you're sending + `ssl_certificate_id`. type: string - default: NORMAL + default: WILDCARD enum: - NORMAL - WILDCARD @@ -17411,6 +17517,52 @@ components: description: Whether it is the default version of the web server type: boolean type: object + HostingSslCertificateCanInstall: + description: >- + Whether an SSL certificate can be installed on the hosting now, and why + not + required: + - hosting_id + - can_install + - reason + - message + - dns_delegation + properties: + hosting_id: + description: Id of the hosting (`GET /api/v3/hostings/{hosting_id}`) + type: integer + can_install: + description: >- + Whether the installation can start now with `POST + /api/v3/hostings/{hosting_id}/ssl-certificates` and the same fields + type: boolean + reason: + description: >- + Why the installation cannot start now; null when it can. + `ALREADY_INSTALLED`: a certificate is already installed on the + destination, remove it first. `CNAME_MISSING`: the DNS of the domain + are not on our nameservers and the CNAME record in `dns_delegation` + is missing. `LIMIT_REACHED`: too many Let's Encrypt installations on + the hosting in the last days. `MAIL_NOT_SUPPORTED`: the mail server + of the hosting does not accept certificates. `OPERATION_RUNNING`: + another installation or operation is running on the hosting + anyOf: + - $ref: '#/components/schemas/HostingSslCertificateCanInstallReason' + - type: 'null' + message: + description: Description of `reason`; null when the installation can start + type: + - string + - 'null' + dns_delegation: + description: >- + CNAME record that delegates the validation of the Let's Encrypt + certificate; null when installing a purchased certificate + (`ssl_certificate_id`) + anyOf: + - $ref: '#/components/schemas/HostingSslDnsDelegation' + - type: 'null' + type: object HostingSslCertificate: description: SSL certificate installed on the website or on the mail of the hosting required: @@ -17477,6 +17629,40 @@ components: - string - 'null' type: object + HostingSslDnsDelegation: + description: >- + CNAME record that delegates the DNS validation of the Let's Encrypt + certificate + required: + - managed_dns + - host + - target + - present + properties: + managed_dns: + description: >- + Whether the DNS zone of the domain is on our nameservers: the + installation creates the CNAME record by itself. When false the + record must be created on the nameservers of the domain before the + installation + type: boolean + host: + description: Name of the CNAME record, in punycode + type: string + target: + description: Target of the CNAME record; null when no delegation is required + type: + - string + - 'null' + present: + description: >- + Whether the public DNS resolvers return the CNAME record with + `target`; null when the DNS zone is on our nameservers, where it is + not checked, or when no delegation is required + type: + - boolean + - 'null' + type: object Hosting: description: >- Hosting of the account: a main hosting, bought with a purchase, or one @@ -20212,6 +20398,7 @@ components: - quantity - activation_price - renew_price + - renew_price_discounted - restore_price - date_activation - date_activation_start @@ -20295,6 +20482,14 @@ components: anyOf: - $ref: '#/components/schemas/Amount' - type: 'null' + renew_price_discounted: + description: >- + Renew price with the discount granted to the purchase by a discount + promotion, for the same recurrence as `renew_price`; null when the + purchase has no discount, and for sub-accounts. + anyOf: + - $ref: '#/components/schemas/Amount' + - type: 'null' restore_price: description: >- Restore/reactivation price (applied on domains only); null for @@ -24162,6 +24357,30 @@ components: description: 'Always null: only the paginated lists have a `meta`' const: null type: object + HostingSslCertificateCanInstallResponse: + description: Successful response with one `HostingSslCertificateCanInstall` + required: + - error + - message + - data + - meta + properties: + error: + description: Always 0 in a successful response + type: integer + const: 0 + message: + description: Always null in a successful response + type: + - string + - 'null' + data: + $ref: '#/components/schemas/HostingSslCertificateCanInstall' + description: Resource returned by the operation + meta: + description: 'Always null: only the paginated lists have a `meta`' + const: null + type: object HostingSslCertificateListResponse: description: Successful response with a list of `HostingSslCertificate` required: diff --git a/src/shellrent_sdk/client/api/hosting/can_install_hosting_ssl_certificate.py b/src/shellrent_sdk/client/api/hosting/can_install_hosting_ssl_certificate.py new file mode 100644 index 0000000..4bbedf1 --- /dev/null +++ b/src/shellrent_sdk/client/api/hosting/can_install_hosting_ssl_certificate.py @@ -0,0 +1,299 @@ +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.api_error import ApiError +from ...models.can_install_hosting_ssl_certificate_cn_type import ( + CanInstallHostingSslCertificateCnType, +) +from ...models.can_install_hosting_ssl_certificate_destination_type import ( + CanInstallHostingSslCertificateDestinationType, +) +from ...models.hosting_ssl_certificate_can_install_response import ( + HostingSslCertificateCanInstallResponse, +) +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + hosting_id: int, + *, + ssl_certificate_id: int | None | Unset = UNSET, + destination_type: CanInstallHostingSslCertificateDestinationType | Unset = UNSET, + cn_type: CanInstallHostingSslCertificateCnType + | Unset = CanInstallHostingSslCertificateCnType.WILDCARD, +) -> dict[str, Any]: + + params: dict[str, Any] = {} + + json_ssl_certificate_id: int | None | Unset + if isinstance(ssl_certificate_id, Unset): + json_ssl_certificate_id = UNSET + else: + json_ssl_certificate_id = ssl_certificate_id + params["ssl_certificate_id"] = json_ssl_certificate_id + + json_destination_type: str | Unset = UNSET + if not isinstance(destination_type, Unset): + json_destination_type = destination_type.value + + params["destination_type"] = json_destination_type + + json_cn_type: str | Unset = UNSET + if not isinstance(cn_type, Unset): + json_cn_type = cn_type.value + + params["cn_type"] = json_cn_type + + params = {k: v for k, v in params.items() if v is not UNSET and v is not None} + + _kwargs: dict[str, Any] = { + "method": "get", + "url": "/api/v3/hostings/{hosting_id}/ssl-certificates/can-install".format( + hosting_id=quote(str(hosting_id), safe=""), + ), + "params": params, + } + + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> ApiError | HostingSslCertificateCanInstallResponse | None: + if response.status_code == 200: + response_200 = HostingSslCertificateCanInstallResponse.from_dict(response.json()) + + return response_200 + + if response.status_code == 400: + response_400 = ApiError.from_dict(response.json()) + + return response_400 + + if response.status_code == 401: + response_401 = ApiError.from_dict(response.json()) + + return response_401 + + if response.status_code == 403: + response_403 = ApiError.from_dict(response.json()) + + return response_403 + + if response.status_code == 404: + response_404 = ApiError.from_dict(response.json()) + + return response_404 + + if response.status_code == 429: + response_429 = ApiError.from_dict(response.json()) + + return response_429 + + if response.status_code == 500: + response_500 = ApiError.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[ApiError | HostingSslCertificateCanInstallResponse]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + hosting_id: int, + *, + client: AuthenticatedClient, + ssl_certificate_id: int | None | Unset = UNSET, + destination_type: CanInstallHostingSslCertificateDestinationType | Unset = UNSET, + cn_type: CanInstallHostingSslCertificateCnType + | Unset = CanInstallHostingSslCertificateCnType.WILDCARD, +) -> Response[ApiError | HostingSslCertificateCanInstallResponse]: + """Can install SSL certificate on hosting + + Checks, with the rules of the installation (`POST /api/v3/hostings/{hosting_id}/ssl-certificates`) + and its fields in the query, whether the SSL certificate can be installed on the hosting now, and + why not: a certificate already installed, the CNAME record that delegates the validation of Let's + Encrypt missing from DNS that are not on our nameservers (`dns_delegation` has the record to + create), the limit of Let's Encrypt installations, an operation running. With DNS on our nameservers + the installation creates the CNAME record by itself. Invalid fields respond 400, as in the + installation + + Args: + hosting_id (int): + ssl_certificate_id (int | None | Unset): + destination_type (CanInstallHostingSslCertificateDestinationType | Unset): + cn_type (CanInstallHostingSslCertificateCnType | Unset): Default: + CanInstallHostingSslCertificateCnType.WILDCARD. + + 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[ApiError | HostingSslCertificateCanInstallResponse] + """ + + kwargs = _get_kwargs( + hosting_id=hosting_id, + ssl_certificate_id=ssl_certificate_id, + destination_type=destination_type, + cn_type=cn_type, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + hosting_id: int, + *, + client: AuthenticatedClient, + ssl_certificate_id: int | None | Unset = UNSET, + destination_type: CanInstallHostingSslCertificateDestinationType | Unset = UNSET, + cn_type: CanInstallHostingSslCertificateCnType + | Unset = CanInstallHostingSslCertificateCnType.WILDCARD, +) -> ApiError | HostingSslCertificateCanInstallResponse | None: + """Can install SSL certificate on hosting + + Checks, with the rules of the installation (`POST /api/v3/hostings/{hosting_id}/ssl-certificates`) + and its fields in the query, whether the SSL certificate can be installed on the hosting now, and + why not: a certificate already installed, the CNAME record that delegates the validation of Let's + Encrypt missing from DNS that are not on our nameservers (`dns_delegation` has the record to + create), the limit of Let's Encrypt installations, an operation running. With DNS on our nameservers + the installation creates the CNAME record by itself. Invalid fields respond 400, as in the + installation + + Args: + hosting_id (int): + ssl_certificate_id (int | None | Unset): + destination_type (CanInstallHostingSslCertificateDestinationType | Unset): + cn_type (CanInstallHostingSslCertificateCnType | Unset): Default: + CanInstallHostingSslCertificateCnType.WILDCARD. + + 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: + ApiError | HostingSslCertificateCanInstallResponse + """ + + return sync_detailed( + hosting_id=hosting_id, + client=client, + ssl_certificate_id=ssl_certificate_id, + destination_type=destination_type, + cn_type=cn_type, + ).parsed + + +async def asyncio_detailed( + hosting_id: int, + *, + client: AuthenticatedClient, + ssl_certificate_id: int | None | Unset = UNSET, + destination_type: CanInstallHostingSslCertificateDestinationType | Unset = UNSET, + cn_type: CanInstallHostingSslCertificateCnType + | Unset = CanInstallHostingSslCertificateCnType.WILDCARD, +) -> Response[ApiError | HostingSslCertificateCanInstallResponse]: + """Can install SSL certificate on hosting + + Checks, with the rules of the installation (`POST /api/v3/hostings/{hosting_id}/ssl-certificates`) + and its fields in the query, whether the SSL certificate can be installed on the hosting now, and + why not: a certificate already installed, the CNAME record that delegates the validation of Let's + Encrypt missing from DNS that are not on our nameservers (`dns_delegation` has the record to + create), the limit of Let's Encrypt installations, an operation running. With DNS on our nameservers + the installation creates the CNAME record by itself. Invalid fields respond 400, as in the + installation + + Args: + hosting_id (int): + ssl_certificate_id (int | None | Unset): + destination_type (CanInstallHostingSslCertificateDestinationType | Unset): + cn_type (CanInstallHostingSslCertificateCnType | Unset): Default: + CanInstallHostingSslCertificateCnType.WILDCARD. + + 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[ApiError | HostingSslCertificateCanInstallResponse] + """ + + kwargs = _get_kwargs( + hosting_id=hosting_id, + ssl_certificate_id=ssl_certificate_id, + destination_type=destination_type, + cn_type=cn_type, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + hosting_id: int, + *, + client: AuthenticatedClient, + ssl_certificate_id: int | None | Unset = UNSET, + destination_type: CanInstallHostingSslCertificateDestinationType | Unset = UNSET, + cn_type: CanInstallHostingSslCertificateCnType + | Unset = CanInstallHostingSslCertificateCnType.WILDCARD, +) -> ApiError | HostingSslCertificateCanInstallResponse | None: + """Can install SSL certificate on hosting + + Checks, with the rules of the installation (`POST /api/v3/hostings/{hosting_id}/ssl-certificates`) + and its fields in the query, whether the SSL certificate can be installed on the hosting now, and + why not: a certificate already installed, the CNAME record that delegates the validation of Let's + Encrypt missing from DNS that are not on our nameservers (`dns_delegation` has the record to + create), the limit of Let's Encrypt installations, an operation running. With DNS on our nameservers + the installation creates the CNAME record by itself. Invalid fields respond 400, as in the + installation + + Args: + hosting_id (int): + ssl_certificate_id (int | None | Unset): + destination_type (CanInstallHostingSslCertificateDestinationType | Unset): + cn_type (CanInstallHostingSslCertificateCnType | Unset): Default: + CanInstallHostingSslCertificateCnType.WILDCARD. + + 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: + ApiError | HostingSslCertificateCanInstallResponse + """ + + return ( + await asyncio_detailed( + hosting_id=hosting_id, + client=client, + ssl_certificate_id=ssl_certificate_id, + destination_type=destination_type, + cn_type=cn_type, + ) + ).parsed diff --git a/src/shellrent_sdk/client/api/hosting/install_hosting_ssl_certificate.py b/src/shellrent_sdk/client/api/hosting/install_hosting_ssl_certificate.py index 41bde30..d9e5e03 100644 --- a/src/shellrent_sdk/client/api/hosting/install_hosting_ssl_certificate.py +++ b/src/shellrent_sdk/client/api/hosting/install_hosting_ssl_certificate.py @@ -103,12 +103,19 @@ def sync_detailed( ) -> Response[ApiError | TaskResponse]: """Install SSL certificate on hosting - Install an SSL certificate on hosting or create and install Let's Encrypt + Installs a purchased SSL certificate on the hosting, or issues and installs a Let's Encrypt + certificate, with the rules of the control panel. With DNS that are not on our nameservers, Let's + Encrypt requires the CNAME record that delegates its DNS validation: `GET + /api/v3/hostings/{hosting_id}/ssl-certificates/can-install` tells whether the installation can + start, and which record to create. 409 when a certificate is already installed on the destination, + the CNAME record is missing, the limit of Let's Encrypt installations is reached or an operation is + running; 403 when the mail server of the hosting does not accept certificates Args: hosting_id (int): body (HostingSslCertificateInstallRequest | Unset): SSL certificate to install on the - hosting; with no body, a Let's Encrypt certificate for the website + hosting; with no body, a wildcard Let's Encrypt certificate for the website and the mail + of a main hosting, or for the website of a subdomain Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -138,12 +145,19 @@ def sync( ) -> ApiError | TaskResponse | None: """Install SSL certificate on hosting - Install an SSL certificate on hosting or create and install Let's Encrypt + Installs a purchased SSL certificate on the hosting, or issues and installs a Let's Encrypt + certificate, with the rules of the control panel. With DNS that are not on our nameservers, Let's + Encrypt requires the CNAME record that delegates its DNS validation: `GET + /api/v3/hostings/{hosting_id}/ssl-certificates/can-install` tells whether the installation can + start, and which record to create. 409 when a certificate is already installed on the destination, + the CNAME record is missing, the limit of Let's Encrypt installations is reached or an operation is + running; 403 when the mail server of the hosting does not accept certificates Args: hosting_id (int): body (HostingSslCertificateInstallRequest | Unset): SSL certificate to install on the - hosting; with no body, a Let's Encrypt certificate for the website + hosting; with no body, a wildcard Let's Encrypt certificate for the website and the mail + of a main hosting, or for the website of a subdomain Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -168,12 +182,19 @@ async def asyncio_detailed( ) -> Response[ApiError | TaskResponse]: """Install SSL certificate on hosting - Install an SSL certificate on hosting or create and install Let's Encrypt + Installs a purchased SSL certificate on the hosting, or issues and installs a Let's Encrypt + certificate, with the rules of the control panel. With DNS that are not on our nameservers, Let's + Encrypt requires the CNAME record that delegates its DNS validation: `GET + /api/v3/hostings/{hosting_id}/ssl-certificates/can-install` tells whether the installation can + start, and which record to create. 409 when a certificate is already installed on the destination, + the CNAME record is missing, the limit of Let's Encrypt installations is reached or an operation is + running; 403 when the mail server of the hosting does not accept certificates Args: hosting_id (int): body (HostingSslCertificateInstallRequest | Unset): SSL certificate to install on the - hosting; with no body, a Let's Encrypt certificate for the website + hosting; with no body, a wildcard Let's Encrypt certificate for the website and the mail + of a main hosting, or for the website of a subdomain Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -201,12 +222,19 @@ async def asyncio( ) -> ApiError | TaskResponse | None: """Install SSL certificate on hosting - Install an SSL certificate on hosting or create and install Let's Encrypt + Installs a purchased SSL certificate on the hosting, or issues and installs a Let's Encrypt + certificate, with the rules of the control panel. With DNS that are not on our nameservers, Let's + Encrypt requires the CNAME record that delegates its DNS validation: `GET + /api/v3/hostings/{hosting_id}/ssl-certificates/can-install` tells whether the installation can + start, and which record to create. 409 when a certificate is already installed on the destination, + the CNAME record is missing, the limit of Let's Encrypt installations is reached or an operation is + running; 403 when the mail server of the hosting does not accept certificates Args: hosting_id (int): body (HostingSslCertificateInstallRequest | Unset): SSL certificate to install on the - hosting; with no body, a Let's Encrypt certificate for the website + hosting; with no body, a wildcard Let's Encrypt certificate for the website and the mail + of a main hosting, or for the website of a subdomain Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. diff --git a/src/shellrent_sdk/client/models/__init__.py b/src/shellrent_sdk/client/models/__init__.py index 344deb2..b85e554 100644 --- a/src/shellrent_sdk/client/models/__init__.py +++ b/src/shellrent_sdk/client/models/__init__.py @@ -72,6 +72,10 @@ ) from .buy_service_veeam_baas_request import BuyServiceVeeamBaasRequest from .buy_service_veeam_baas_request_configuration import BuyServiceVeeamBaasRequestConfiguration +from .can_install_hosting_ssl_certificate_cn_type import CanInstallHostingSslCertificateCnType +from .can_install_hosting_ssl_certificate_destination_type import ( + CanInstallHostingSslCertificateDestinationType, +) from .cloud_storage import CloudStorage from .cloud_storage_access import CloudStorageAccess from .cloud_storage_access_create_request import CloudStorageAccessCreateRequest @@ -231,6 +235,9 @@ from .hosting_php_version_update_request import HostingPhpVersionUpdateRequest from .hosting_response import HostingResponse from .hosting_ssl_certificate import HostingSslCertificate +from .hosting_ssl_certificate_can_install import HostingSslCertificateCanInstall +from .hosting_ssl_certificate_can_install_reason import HostingSslCertificateCanInstallReason +from .hosting_ssl_certificate_can_install_response import HostingSslCertificateCanInstallResponse from .hosting_ssl_certificate_install_request import HostingSslCertificateInstallRequest from .hosting_ssl_certificate_install_request_cn_type import ( HostingSslCertificateInstallRequestCnType, @@ -240,6 +247,7 @@ ) from .hosting_ssl_certificate_list_response import HostingSslCertificateListResponse from .hosting_ssl_certificate_response import HostingSslCertificateResponse +from .hosting_ssl_dns_delegation import HostingSslDnsDelegation from .hosting_subdomain_create_request import HostingSubdomainCreateRequest from .invoice import Invoice from .invoice_paginated_list_response import InvoicePaginatedListResponse @@ -568,6 +576,8 @@ "BuyServiceSslCertificateRequestDataAdminTitle", "BuyServiceVeeamBaasRequest", "BuyServiceVeeamBaasRequestConfiguration", + "CanInstallHostingSslCertificateCnType", + "CanInstallHostingSslCertificateDestinationType", "CloudStorage", "CloudStorageAccess", "CloudStorageAccessCreateRequest", @@ -709,11 +719,15 @@ "HostingPhpVersionUpdateRequest", "HostingResponse", "HostingSslCertificate", + "HostingSslCertificateCanInstall", + "HostingSslCertificateCanInstallReason", + "HostingSslCertificateCanInstallResponse", "HostingSslCertificateInstallRequest", "HostingSslCertificateInstallRequestCnType", "HostingSslCertificateInstallRequestDestinationType", "HostingSslCertificateListResponse", "HostingSslCertificateResponse", + "HostingSslDnsDelegation", "HostingSubdomainCreateRequest", "Invoice", "InvoicePaginatedListResponse", diff --git a/src/shellrent_sdk/client/models/can_install_hosting_ssl_certificate_cn_type.py b/src/shellrent_sdk/client/models/can_install_hosting_ssl_certificate_cn_type.py new file mode 100644 index 0000000..0d5ce67 --- /dev/null +++ b/src/shellrent_sdk/client/models/can_install_hosting_ssl_certificate_cn_type.py @@ -0,0 +1,9 @@ +from enum import StrEnum + + +class CanInstallHostingSslCertificateCnType(StrEnum): + NORMAL = "NORMAL" + WILDCARD = "WILDCARD" + + def __str__(self) -> str: + return str(self.value) diff --git a/src/shellrent_sdk/client/models/can_install_hosting_ssl_certificate_destination_type.py b/src/shellrent_sdk/client/models/can_install_hosting_ssl_certificate_destination_type.py new file mode 100644 index 0000000..d2b93ac --- /dev/null +++ b/src/shellrent_sdk/client/models/can_install_hosting_ssl_certificate_destination_type.py @@ -0,0 +1,10 @@ +from enum import StrEnum + + +class CanInstallHostingSslCertificateDestinationType(StrEnum): + ALL = "all" + MAIL = "mail" + WEB = "web" + + def __str__(self) -> str: + return str(self.value) diff --git a/src/shellrent_sdk/client/models/hosting_ssl_certificate_can_install.py b/src/shellrent_sdk/client/models/hosting_ssl_certificate_can_install.py new file mode 100644 index 0000000..ec6d3ef --- /dev/null +++ b/src/shellrent_sdk/client/models/hosting_ssl_certificate_can_install.py @@ -0,0 +1,149 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, Self, TypeVar, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..models.hosting_ssl_certificate_can_install_reason import ( + HostingSslCertificateCanInstallReason, +) + +if TYPE_CHECKING: + from ..models.hosting_ssl_dns_delegation import HostingSslDnsDelegation + + +T = TypeVar("T", bound="HostingSslCertificateCanInstall") + + +@_attrs_define +class HostingSslCertificateCanInstall: + """Whether an SSL certificate can be installed on the hosting now, and why not""" + + hosting_id: int + """ Id of the hosting (`GET /api/v3/hostings/{hosting_id}`) """ + can_install: bool + """ Whether the installation can start now with `POST /api/v3/hostings/{hosting_id}/ssl-certificates` and the + same fields """ + reason: HostingSslCertificateCanInstallReason | None + """ Why the installation cannot start now; null when it can. `ALREADY_INSTALLED`: a certificate is already + installed on the destination, remove it first. `CNAME_MISSING`: the DNS of the domain are not on our nameservers + and the CNAME record in `dns_delegation` is missing. `LIMIT_REACHED`: too many Let's Encrypt installations on + the hosting in the last days. `MAIL_NOT_SUPPORTED`: the mail server of the hosting does not accept certificates. + `OPERATION_RUNNING`: another installation or operation is running on the hosting """ + message: None | str + """ Description of `reason`; null when the installation can start """ + dns_delegation: HostingSslDnsDelegation | None + """ CNAME record that delegates the validation of the Let's Encrypt certificate; null when installing a + purchased certificate (`ssl_certificate_id`) """ + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + from ..models.hosting_ssl_dns_delegation import HostingSslDnsDelegation + + hosting_id = self.hosting_id + + can_install = self.can_install + + reason: None | str + if isinstance(self.reason, HostingSslCertificateCanInstallReason): + reason = self.reason.value + else: + reason = self.reason + + message: None | str + message = self.message + + dns_delegation: dict[str, Any] | None + if isinstance(self.dns_delegation, HostingSslDnsDelegation): + dns_delegation = self.dns_delegation.to_dict() + else: + dns_delegation = self.dns_delegation + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "hosting_id": hosting_id, + "can_install": can_install, + "reason": reason, + "message": message, + "dns_delegation": dns_delegation, + } + ) + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + from ..models.hosting_ssl_dns_delegation import HostingSslDnsDelegation + + d = dict(src_dict) + hosting_id = d.pop("hosting_id") + + can_install = d.pop("can_install") + + def _parse_reason(data: object) -> HostingSslCertificateCanInstallReason | None: + if data is None: + return data + try: + if not isinstance(data, str): + raise TypeError() + reason_type_0 = HostingSslCertificateCanInstallReason(data) + + return reason_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(HostingSslCertificateCanInstallReason | None, data) + + reason = _parse_reason(d.pop("reason")) + + def _parse_message(data: object) -> None | str: + if data is None: + return data + return cast(None | str, data) + + message = _parse_message(d.pop("message")) + + def _parse_dns_delegation(data: object) -> HostingSslDnsDelegation | None: + if data is None: + return data + try: + if not isinstance(data, dict): + raise TypeError() + dns_delegation_type_0 = HostingSslDnsDelegation.from_dict(data) + + return dns_delegation_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(HostingSslDnsDelegation | None, data) + + dns_delegation = _parse_dns_delegation(d.pop("dns_delegation")) + + hosting_ssl_certificate_can_install = cls( + hosting_id=hosting_id, + can_install=can_install, + reason=reason, + message=message, + dns_delegation=dns_delegation, + ) + + hosting_ssl_certificate_can_install.additional_properties = d + return hosting_ssl_certificate_can_install + + @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/src/shellrent_sdk/client/models/hosting_ssl_certificate_can_install_reason.py b/src/shellrent_sdk/client/models/hosting_ssl_certificate_can_install_reason.py new file mode 100644 index 0000000..f4b2e55 --- /dev/null +++ b/src/shellrent_sdk/client/models/hosting_ssl_certificate_can_install_reason.py @@ -0,0 +1,12 @@ +from enum import StrEnum + + +class HostingSslCertificateCanInstallReason(StrEnum): + ALREADY_INSTALLED = "ALREADY_INSTALLED" + CNAME_MISSING = "CNAME_MISSING" + LIMIT_REACHED = "LIMIT_REACHED" + MAIL_NOT_SUPPORTED = "MAIL_NOT_SUPPORTED" + OPERATION_RUNNING = "OPERATION_RUNNING" + + def __str__(self) -> str: + return str(self.value) diff --git a/src/shellrent_sdk/client/models/hosting_ssl_certificate_can_install_response.py b/src/shellrent_sdk/client/models/hosting_ssl_certificate_can_install_response.py new file mode 100644 index 0000000..bb9bb41 --- /dev/null +++ b/src/shellrent_sdk/client/models/hosting_ssl_certificate_can_install_response.py @@ -0,0 +1,99 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, Literal, Self, TypeVar, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +if TYPE_CHECKING: + from ..models.hosting_ssl_certificate_can_install import HostingSslCertificateCanInstall + + +T = TypeVar("T", bound="HostingSslCertificateCanInstallResponse") + + +@_attrs_define +class HostingSslCertificateCanInstallResponse: + """Successful response with one `HostingSslCertificateCanInstall`""" + + error: Literal[0] + """ Always 0 in a successful response """ + message: None | str + """ Always null in a successful response """ + data: HostingSslCertificateCanInstall + """ Whether an SSL certificate can be installed on the hosting now, and why not """ + meta: Any + """ Always null: only the paginated lists have a `meta` """ + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + error = self.error + + message: None | str + message = self.message + + data = self.data.to_dict() + + meta = self.meta + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "error": error, + "message": message, + "data": data, + "meta": meta, + } + ) + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + from ..models.hosting_ssl_certificate_can_install import ( + HostingSslCertificateCanInstall, + ) + + d = dict(src_dict) + error = cast(Literal[0], d.pop("error")) + if error != 0: + raise ValueError(f"error must match const 0, got '{error}'") + + def _parse_message(data: object) -> None | str: + if data is None: + return data + return cast(None | str, data) + + message = _parse_message(d.pop("message")) + + data = HostingSslCertificateCanInstall.from_dict(d.pop("data")) + + meta = d.pop("meta") + + hosting_ssl_certificate_can_install_response = cls( + error=error, + message=message, + data=data, + meta=meta, + ) + + hosting_ssl_certificate_can_install_response.additional_properties = d + return hosting_ssl_certificate_can_install_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/src/shellrent_sdk/client/models/hosting_ssl_certificate_install_request.py b/src/shellrent_sdk/client/models/hosting_ssl_certificate_install_request.py index 76deb66..066e97e 100644 --- a/src/shellrent_sdk/client/models/hosting_ssl_certificate_install_request.py +++ b/src/shellrent_sdk/client/models/hosting_ssl_certificate_install_request.py @@ -18,21 +18,26 @@ @_attrs_define class HostingSslCertificateInstallRequest: - """SSL certificate to install on the hosting; with no body, a Let's Encrypt certificate for the website""" + """SSL certificate to install on the hosting; with no body, a wildcard Let's Encrypt certificate for the website and + the mail of a main hosting, or for the website of a subdomain + + """ ssl_certificate_id: int | None | Unset = UNSET - """ Id of a purchased SSL certificate to install (`GET /api/v3/ssl-certificates/{ssl_certificate_id}`); omit it, - or send null, to install a Let's Encrypt certificate, issued if the hosting has none """ - destination_type: HostingSslCertificateInstallRequestDestinationType | Unset = ( - HostingSslCertificateInstallRequestDestinationType.WEB - ) - """ Where to install the certificate: `web` for the website, `mail` for the mail servers, `all` for both. `mail` - and `all` require `cn_type` `WILDCARD`; on a subdomain only `web` is allowed """ + """ Id of a purchased SSL certificate to install (`GET /api/v3/ssl-certificates/{ssl_certificate_id}`): an + active certificate of the owner of the hosting, a wildcard one or one with SAN to install it also on the mail + servers. Omit it, or send null, to install a Let's Encrypt certificate, issued if the hosting has none: when the + DNS of the domain are not on our nameservers, the issue requires the CNAME record that delegates the DNS + validation (`GET /api/v3/hostings/{hosting_id}/ssl-certificates/can-install`) """ + destination_type: HostingSslCertificateInstallRequestDestinationType | Unset = UNSET + """ Where to install the certificate: `web` for the website, `mail` for the mail servers, `all` for both. When + omitted, `all` on a main hosting and `web` on a subdomain, where only `web` is allowed. `mail` and `all` require + a Let's Encrypt `cn_type` `WILDCARD` """ cn_type: HostingSslCertificateInstallRequestCnType | Unset = ( - HostingSslCertificateInstallRequestCnType.NORMAL + HostingSslCertificateInstallRequestCnType.WILDCARD ) """ Type of the certificate: `NORMAL`, or `WILDCARD` for a wildcard certificate. Without `ssl_certificate_id` it - chooses the Let's Encrypt certificate to install """ + chooses the Let's Encrypt certificate to install. Omit this field if you're sending `ssl_certificate_id`. """ def to_dict(self) -> dict[str, Any]: ssl_certificate_id: int | None | Unset diff --git a/src/shellrent_sdk/client/models/hosting_ssl_dns_delegation.py b/src/shellrent_sdk/client/models/hosting_ssl_dns_delegation.py new file mode 100644 index 0000000..bcd9260 --- /dev/null +++ b/src/shellrent_sdk/client/models/hosting_ssl_dns_delegation.py @@ -0,0 +1,97 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, Self, TypeVar, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="HostingSslDnsDelegation") + + +@_attrs_define +class HostingSslDnsDelegation: + """CNAME record that delegates the DNS validation of the Let's Encrypt certificate""" + + managed_dns: bool + """ Whether the DNS zone of the domain is on our nameservers: the installation creates the CNAME record by + itself. When false the record must be created on the nameservers of the domain before the installation """ + host: str + """ Name of the CNAME record, in punycode """ + target: None | str + """ Target of the CNAME record; null when no delegation is required """ + present: bool | None + """ Whether the public DNS resolvers return the CNAME record with `target`; null when the DNS zone is on our + nameservers, where it is not checked, or when no delegation is required """ + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + managed_dns = self.managed_dns + + host = self.host + + target: None | str + target = self.target + + present: bool | None + present = self.present + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "managed_dns": managed_dns, + "host": host, + "target": target, + "present": present, + } + ) + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + d = dict(src_dict) + managed_dns = d.pop("managed_dns") + + host = d.pop("host") + + def _parse_target(data: object) -> None | str: + if data is None: + return data + return cast(None | str, data) + + target = _parse_target(d.pop("target")) + + def _parse_present(data: object) -> bool | None: + if data is None: + return data + return cast(bool | None, data) + + present = _parse_present(d.pop("present")) + + hosting_ssl_dns_delegation = cls( + managed_dns=managed_dns, + host=host, + target=target, + present=present, + ) + + hosting_ssl_dns_delegation.additional_properties = d + return hosting_ssl_dns_delegation + + @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/src/shellrent_sdk/client/models/o_auth_error.py b/src/shellrent_sdk/client/models/o_auth_error.py index 7a890f4..6f86f1d 100644 --- a/src/shellrent_sdk/client/models/o_auth_error.py +++ b/src/shellrent_sdk/client/models/o_auth_error.py @@ -16,7 +16,8 @@ class OAuthError: """Error of the token endpoint, in the OAuth2 format (RFC 6749, section 5.2)""" error: str - """ OAuth2 error code, for example `invalid_client`, `invalid_scope` or `rate_limited` """ + """ OAuth2 error code, for example `invalid_client`, `invalid_scope`, `unauthorized_client` or `rate_limited` + """ error_description: None | str | Unset = UNSET """ Description of the error, for the developers of the client """ error_uri: None | str | Unset = UNSET diff --git a/src/shellrent_sdk/client/models/purchase.py b/src/shellrent_sdk/client/models/purchase.py index 80952df..e5472f4 100644 --- a/src/shellrent_sdk/client/models/purchase.py +++ b/src/shellrent_sdk/client/models/purchase.py @@ -54,6 +54,9 @@ class Purchase: """ Purchase price; null for sub-accounts. """ renew_price: Amount | None """ Renew price (applied on recurring purchases only); null for sub-accounts. """ + renew_price_discounted: Amount | None + """ Renew price with the discount granted to the purchase by a discount promotion, for the same recurrence as + `renew_price`; null when the purchase has no discount, and for sub-accounts. """ restore_price: Amount | None """ Restore/reactivation price (applied on domains only); null for sub-accounts. """ date_activation: datetime.date | None @@ -149,6 +152,12 @@ def to_dict(self) -> dict[str, Any]: else: renew_price = self.renew_price + renew_price_discounted: dict[str, Any] | None + if isinstance(self.renew_price_discounted, Amount): + renew_price_discounted = self.renew_price_discounted.to_dict() + else: + renew_price_discounted = self.renew_price_discounted + restore_price: dict[str, Any] | None if isinstance(self.restore_price, Amount): restore_price = self.restore_price.to_dict() @@ -251,6 +260,7 @@ def to_dict(self) -> dict[str, Any]: "quantity": quantity, "activation_price": activation_price, "renew_price": renew_price, + "renew_price_discounted": renew_price_discounted, "restore_price": restore_price, "date_activation": date_activation, "date_activation_start": date_activation_start, @@ -370,6 +380,21 @@ def _parse_renew_price(data: object) -> Amount | None: renew_price = _parse_renew_price(d.pop("renew_price")) + def _parse_renew_price_discounted(data: object) -> Amount | None: + if data is None: + return data + try: + if not isinstance(data, dict): + raise TypeError() + renew_price_discounted_type_0 = Amount.from_dict(data) + + return renew_price_discounted_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(Amount | None, data) + + renew_price_discounted = _parse_renew_price_discounted(d.pop("renew_price_discounted")) + def _parse_restore_price(data: object) -> Amount | None: if data is None: return data @@ -568,6 +593,7 @@ def _parse_veeam_baas_id(data: object) -> int | None: quantity=quantity, activation_price=activation_price, renew_price=renew_price, + renew_price_discounted=renew_price_discounted, restore_price=restore_price, date_activation=date_activation, date_activation_start=date_activation_start, From 778a90b01ee31fc31ac167d5414e95dff93a2edd Mon Sep 17 00:00:00 2001 From: Tobia Zanarella Date: Fri, 30 Oct 2026 12:44:15 +0100 Subject: [PATCH 2/3] Regenerate from the specification with the HOSTING_NOT_ACTIVE reason of can-install The hosting SSL installation check gets a new reason, HOSTING_NOT_ACTIVE, for a hosting whose activation has not been completed yet (it was reported as OPERATION_RUNNING). Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 +- spec/openapi.yaml | 22 ++++++++++------- .../can_install_hosting_ssl_certificate.py | 24 +++++++++---------- .../install_hosting_ssl_certificate.py | 20 +++++++++------- .../hosting_ssl_certificate_can_install.py | 5 ++-- ...ting_ssl_certificate_can_install_reason.py | 1 + 6 files changed, 42 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a7b6d5..3a0e799 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,7 @@ renew price of the purchases. No breaking changes in names or signatures. nameservers. - `HostingSslCertificateCanInstall`, `HostingSslCertificateCanInstallResponse`, `HostingSslDnsDelegation`, the enum `HostingSslCertificateCanInstallReason` (`ALREADY_INSTALLED`, `CNAME_MISSING`, `LIMIT_REACHED`, - `MAIL_NOT_SUPPORTED`, `OPERATION_RUNNING`) and the enums of the query parameters + `HOSTING_NOT_ACTIVE`, `MAIL_NOT_SUPPORTED`, `OPERATION_RUNNING`) and the enums of the query parameters `CanInstallHostingSslCertificateDestinationType` and `CanInstallHostingSslCertificateCnType`. - `Purchase.renew_price_discounted` (`Amount | None`): the renew price with the discount of the discount promotion applied to the purchase; `None` without a discount and for sub-accounts. diff --git a/spec/openapi.yaml b/spec/openapi.yaml index b409afe..7a34aaa 100644 --- a/spec/openapi.yaml +++ b/spec/openapi.yaml @@ -3131,9 +3131,9 @@ paths: /api/v3/hostings/{hosting_id}/ssl-certificates/can-install` tells whether the installation can start, and which record to create. 409 when a certificate is already installed on the destination, the CNAME record - is missing, the limit of Let's Encrypt installations is reached or an - operation is running; 403 when the mail server of the hosting does not - accept certificates + is missing, the limit of Let's Encrypt installations is reached, the + hosting is not active yet or an operation is running; 403 when the mail + server of the hosting does not accept certificates operationId: installHostingSslCertificate parameters: - $ref: '#/components/parameters/HostingId' @@ -3264,9 +3264,10 @@ paths: and why not: a certificate already installed, the CNAME record that delegates the validation of Let's Encrypt missing from DNS that are not on our nameservers (`dns_delegation` has the record to create), the - limit of Let's Encrypt installations, an operation running. With DNS on - our nameservers the installation creates the CNAME record by itself. - Invalid fields respond 400, as in the installation + limit of Let's Encrypt installations, the hosting not active yet, an + operation running. With DNS on our nameservers the installation creates + the CNAME record by itself. Invalid fields respond 400, as in the + installation operationId: canInstallHostingSslCertificate parameters: - $ref: '#/components/parameters/HostingId' @@ -12622,6 +12623,7 @@ components: - ALREADY_INSTALLED - CNAME_MISSING - LIMIT_REACHED + - HOSTING_NOT_ACTIVE - MAIL_NOT_SUPPORTED - OPERATION_RUNNING example: CNAME_MISSING @@ -17543,9 +17545,11 @@ components: destination, remove it first. `CNAME_MISSING`: the DNS of the domain are not on our nameservers and the CNAME record in `dns_delegation` is missing. `LIMIT_REACHED`: too many Let's Encrypt installations on - the hosting in the last days. `MAIL_NOT_SUPPORTED`: the mail server - of the hosting does not accept certificates. `OPERATION_RUNNING`: - another installation or operation is running on the hosting + the hosting in the last days. `HOSTING_NOT_ACTIVE`: the activation + of the hosting has not been completed yet. `MAIL_NOT_SUPPORTED`: the + mail server of the hosting does not accept certificates. + `OPERATION_RUNNING`: another installation or operation is running on + the hosting anyOf: - $ref: '#/components/schemas/HostingSslCertificateCanInstallReason' - type: 'null' diff --git a/src/shellrent_sdk/client/api/hosting/can_install_hosting_ssl_certificate.py b/src/shellrent_sdk/client/api/hosting/can_install_hosting_ssl_certificate.py index 4bbedf1..5f50cc8 100644 --- a/src/shellrent_sdk/client/api/hosting/can_install_hosting_ssl_certificate.py +++ b/src/shellrent_sdk/client/api/hosting/can_install_hosting_ssl_certificate.py @@ -132,9 +132,9 @@ def sync_detailed( and its fields in the query, whether the SSL certificate can be installed on the hosting now, and why not: a certificate already installed, the CNAME record that delegates the validation of Let's Encrypt missing from DNS that are not on our nameservers (`dns_delegation` has the record to - create), the limit of Let's Encrypt installations, an operation running. With DNS on our nameservers - the installation creates the CNAME record by itself. Invalid fields respond 400, as in the - installation + create), the limit of Let's Encrypt installations, the hosting not active yet, an operation running. + With DNS on our nameservers the installation creates the CNAME record by itself. Invalid fields + respond 400, as in the installation Args: hosting_id (int): @@ -180,9 +180,9 @@ def sync( and its fields in the query, whether the SSL certificate can be installed on the hosting now, and why not: a certificate already installed, the CNAME record that delegates the validation of Let's Encrypt missing from DNS that are not on our nameservers (`dns_delegation` has the record to - create), the limit of Let's Encrypt installations, an operation running. With DNS on our nameservers - the installation creates the CNAME record by itself. Invalid fields respond 400, as in the - installation + create), the limit of Let's Encrypt installations, the hosting not active yet, an operation running. + With DNS on our nameservers the installation creates the CNAME record by itself. Invalid fields + respond 400, as in the installation Args: hosting_id (int): @@ -223,9 +223,9 @@ async def asyncio_detailed( and its fields in the query, whether the SSL certificate can be installed on the hosting now, and why not: a certificate already installed, the CNAME record that delegates the validation of Let's Encrypt missing from DNS that are not on our nameservers (`dns_delegation` has the record to - create), the limit of Let's Encrypt installations, an operation running. With DNS on our nameservers - the installation creates the CNAME record by itself. Invalid fields respond 400, as in the - installation + create), the limit of Let's Encrypt installations, the hosting not active yet, an operation running. + With DNS on our nameservers the installation creates the CNAME record by itself. Invalid fields + respond 400, as in the installation Args: hosting_id (int): @@ -269,9 +269,9 @@ async def asyncio( and its fields in the query, whether the SSL certificate can be installed on the hosting now, and why not: a certificate already installed, the CNAME record that delegates the validation of Let's Encrypt missing from DNS that are not on our nameservers (`dns_delegation` has the record to - create), the limit of Let's Encrypt installations, an operation running. With DNS on our nameservers - the installation creates the CNAME record by itself. Invalid fields respond 400, as in the - installation + create), the limit of Let's Encrypt installations, the hosting not active yet, an operation running. + With DNS on our nameservers the installation creates the CNAME record by itself. Invalid fields + respond 400, as in the installation Args: hosting_id (int): diff --git a/src/shellrent_sdk/client/api/hosting/install_hosting_ssl_certificate.py b/src/shellrent_sdk/client/api/hosting/install_hosting_ssl_certificate.py index d9e5e03..6146465 100644 --- a/src/shellrent_sdk/client/api/hosting/install_hosting_ssl_certificate.py +++ b/src/shellrent_sdk/client/api/hosting/install_hosting_ssl_certificate.py @@ -108,8 +108,9 @@ def sync_detailed( Encrypt requires the CNAME record that delegates its DNS validation: `GET /api/v3/hostings/{hosting_id}/ssl-certificates/can-install` tells whether the installation can start, and which record to create. 409 when a certificate is already installed on the destination, - the CNAME record is missing, the limit of Let's Encrypt installations is reached or an operation is - running; 403 when the mail server of the hosting does not accept certificates + the CNAME record is missing, the limit of Let's Encrypt installations is reached, the hosting is not + active yet or an operation is running; 403 when the mail server of the hosting does not accept + certificates Args: hosting_id (int): @@ -150,8 +151,9 @@ def sync( Encrypt requires the CNAME record that delegates its DNS validation: `GET /api/v3/hostings/{hosting_id}/ssl-certificates/can-install` tells whether the installation can start, and which record to create. 409 when a certificate is already installed on the destination, - the CNAME record is missing, the limit of Let's Encrypt installations is reached or an operation is - running; 403 when the mail server of the hosting does not accept certificates + the CNAME record is missing, the limit of Let's Encrypt installations is reached, the hosting is not + active yet or an operation is running; 403 when the mail server of the hosting does not accept + certificates Args: hosting_id (int): @@ -187,8 +189,9 @@ async def asyncio_detailed( Encrypt requires the CNAME record that delegates its DNS validation: `GET /api/v3/hostings/{hosting_id}/ssl-certificates/can-install` tells whether the installation can start, and which record to create. 409 when a certificate is already installed on the destination, - the CNAME record is missing, the limit of Let's Encrypt installations is reached or an operation is - running; 403 when the mail server of the hosting does not accept certificates + the CNAME record is missing, the limit of Let's Encrypt installations is reached, the hosting is not + active yet or an operation is running; 403 when the mail server of the hosting does not accept + certificates Args: hosting_id (int): @@ -227,8 +230,9 @@ async def asyncio( Encrypt requires the CNAME record that delegates its DNS validation: `GET /api/v3/hostings/{hosting_id}/ssl-certificates/can-install` tells whether the installation can start, and which record to create. 409 when a certificate is already installed on the destination, - the CNAME record is missing, the limit of Let's Encrypt installations is reached or an operation is - running; 403 when the mail server of the hosting does not accept certificates + the CNAME record is missing, the limit of Let's Encrypt installations is reached, the hosting is not + active yet or an operation is running; 403 when the mail server of the hosting does not accept + certificates Args: hosting_id (int): diff --git a/src/shellrent_sdk/client/models/hosting_ssl_certificate_can_install.py b/src/shellrent_sdk/client/models/hosting_ssl_certificate_can_install.py index ec6d3ef..3096dea 100644 --- a/src/shellrent_sdk/client/models/hosting_ssl_certificate_can_install.py +++ b/src/shellrent_sdk/client/models/hosting_ssl_certificate_can_install.py @@ -30,8 +30,9 @@ class HostingSslCertificateCanInstall: """ Why the installation cannot start now; null when it can. `ALREADY_INSTALLED`: a certificate is already installed on the destination, remove it first. `CNAME_MISSING`: the DNS of the domain are not on our nameservers and the CNAME record in `dns_delegation` is missing. `LIMIT_REACHED`: too many Let's Encrypt installations on - the hosting in the last days. `MAIL_NOT_SUPPORTED`: the mail server of the hosting does not accept certificates. - `OPERATION_RUNNING`: another installation or operation is running on the hosting """ + the hosting in the last days. `HOSTING_NOT_ACTIVE`: the activation of the hosting has not been completed yet. + `MAIL_NOT_SUPPORTED`: the mail server of the hosting does not accept certificates. `OPERATION_RUNNING`: another + installation or operation is running on the hosting """ message: None | str """ Description of `reason`; null when the installation can start """ dns_delegation: HostingSslDnsDelegation | None diff --git a/src/shellrent_sdk/client/models/hosting_ssl_certificate_can_install_reason.py b/src/shellrent_sdk/client/models/hosting_ssl_certificate_can_install_reason.py index f4b2e55..b782ba6 100644 --- a/src/shellrent_sdk/client/models/hosting_ssl_certificate_can_install_reason.py +++ b/src/shellrent_sdk/client/models/hosting_ssl_certificate_can_install_reason.py @@ -4,6 +4,7 @@ class HostingSslCertificateCanInstallReason(StrEnum): ALREADY_INSTALLED = "ALREADY_INSTALLED" CNAME_MISSING = "CNAME_MISSING" + HOSTING_NOT_ACTIVE = "HOSTING_NOT_ACTIVE" LIMIT_REACHED = "LIMIT_REACHED" MAIL_NOT_SUPPORTED = "MAIL_NOT_SUPPORTED" OPERATION_RUNNING = "OPERATION_RUNNING" From 4fe71573461de33a617a91fd58ced2b5995683d7 Mon Sep 17 00:00:00 2001 From: Tobia Zanarella Date: Fri, 30 Oct 2026 14:11:50 +0100 Subject: [PATCH 3/3] CHANGELOG: links for 1.1.0 Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a0e799..8be10f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -191,7 +191,8 @@ paths, parameters and models: see the lists below to update the code written for `invalid_client` or, for its rate limit, `rate_limited`. - The `shellrent` command, with `shellrent api` and `shellrent token`. -[Unreleased]: https://github.com/shellrent/shellrent-sdk-python/compare/v1.0.0...HEAD +[Unreleased]: https://github.com/shellrent/shellrent-sdk-python/compare/v1.1.0...HEAD +[1.1.0]: https://github.com/shellrent/shellrent-sdk-python/compare/v1.0.0...v1.1.0 [1.0.0]: https://github.com/shellrent/shellrent-sdk-python/compare/v0.3.0...v1.0.0 [0.3.0]: https://github.com/shellrent/shellrent-sdk-python/compare/v0.2.0...v0.3.0 [0.2.0]: https://github.com/shellrent/shellrent-sdk-python/compare/v0.1.0...v0.2.0