From 7b4a738b0e4bdb6cba6c10b299ccbf46266e76cf Mon Sep 17 00:00:00 2001 From: Scaleway Bot Date: Tue, 4 Feb 2025 17:32:50 +0100 Subject: [PATCH] feat(webhosting): add public search domains (#848) --- .../scaleway_async/webhosting/v1/__init__.py | 26 +++ .../scaleway_async/webhosting/v1/api.py | 128 ++++++++++++- .../scaleway_async/webhosting/v1/content.py | 14 ++ .../webhosting/v1/marshalling.py | 102 ++++++++++ .../scaleway_async/webhosting/v1/types.py | 175 ++++++++++++++++++ .../scaleway_async/webhosting/v1alpha1/api.py | 2 +- scaleway/scaleway/webhosting/v1/__init__.py | 26 +++ scaleway/scaleway/webhosting/v1/api.py | 128 ++++++++++++- scaleway/scaleway/webhosting/v1/content.py | 14 ++ .../scaleway/webhosting/v1/marshalling.py | 102 ++++++++++ scaleway/scaleway/webhosting/v1/types.py | 175 ++++++++++++++++++ scaleway/scaleway/webhosting/v1alpha1/api.py | 2 +- 12 files changed, 888 insertions(+), 6 deletions(-) diff --git a/scaleway-async/scaleway_async/webhosting/v1/__init__.py b/scaleway-async/scaleway_async/webhosting/v1/__init__.py index 0ae2a234..467720f2 100644 --- a/scaleway-async/scaleway_async/webhosting/v1/__init__.py +++ b/scaleway-async/scaleway_async/webhosting/v1/__init__.py @@ -3,6 +3,14 @@ from .types import DnsRecordStatus from .types import DnsRecordType from .types import DnsRecordsStatus +from .types import DomainAction +from .types import DomainAvailabilityAction +from .types import DomainAvailabilityStatus +from .content import DOMAIN_AVAILABILITY_TRANSIENT_STATUSES +from .types import DomainDnsAction +from .types import DomainStatus +from .content import DOMAIN_TRANSIENT_STATUSES +from .types import DomainZoneOwner from .types import HostingStatus from .content import HOSTING_TRANSIENT_STATUSES from .types import HostingSummaryStatus @@ -37,6 +45,7 @@ from .types import HostingSummary from .types import MailAccount from .types import Website +from .types import DomainAvailability from .types import CheckUserOwnsDomainResponse from .types import ControlPanelApiListControlPanelsRequest from .types import DatabaseApiAssignDatabaseUserRequest @@ -52,8 +61,11 @@ from .types import DatabaseApiUnassignDatabaseUserRequest from .types import DnsApiCheckUserOwnsDomainRequest from .types import DnsApiGetDomainDnsRecordsRequest +from .types import DnsApiGetDomainRequest +from .types import DnsApiSearchDomainsRequest from .types import DnsApiSyncDomainDnsRecordsRequest from .types import DnsRecords +from .types import Domain from .types import FtpAccountApiChangeFtpAccountPasswordRequest from .types import FtpAccountApiCreateFtpAccountRequest from .types import FtpAccountApiListFtpAccountsRequest @@ -82,6 +94,7 @@ from .types import OfferApiListOffersRequest from .types import ResetHostingPasswordResponse from .types import ResourceSummary +from .types import SearchDomainsResponse from .types import Session from .types import WebsiteApiListWebsitesRequest from .api import WebhostingV1ControlPanelAPI @@ -97,6 +110,14 @@ "DnsRecordStatus", "DnsRecordType", "DnsRecordsStatus", + "DomainAction", + "DomainAvailabilityAction", + "DomainAvailabilityStatus", + "DOMAIN_AVAILABILITY_TRANSIENT_STATUSES", + "DomainDnsAction", + "DomainStatus", + "DOMAIN_TRANSIENT_STATUSES", + "DomainZoneOwner", "HostingStatus", "HOSTING_TRANSIENT_STATUSES", "HostingSummaryStatus", @@ -131,6 +152,7 @@ "HostingSummary", "MailAccount", "Website", + "DomainAvailability", "CheckUserOwnsDomainResponse", "ControlPanelApiListControlPanelsRequest", "DatabaseApiAssignDatabaseUserRequest", @@ -146,8 +168,11 @@ "DatabaseApiUnassignDatabaseUserRequest", "DnsApiCheckUserOwnsDomainRequest", "DnsApiGetDomainDnsRecordsRequest", + "DnsApiGetDomainRequest", + "DnsApiSearchDomainsRequest", "DnsApiSyncDomainDnsRecordsRequest", "DnsRecords", + "Domain", "FtpAccountApiChangeFtpAccountPasswordRequest", "FtpAccountApiCreateFtpAccountRequest", "FtpAccountApiListFtpAccountsRequest", @@ -176,6 +201,7 @@ "OfferApiListOffersRequest", "ResetHostingPasswordResponse", "ResourceSummary", + "SearchDomainsResponse", "Session", "WebsiteApiListWebsitesRequest", "WebhostingV1ControlPanelAPI", diff --git a/scaleway-async/scaleway_async/webhosting/v1/api.py b/scaleway-async/scaleway_async/webhosting/v1/api.py index c3fbbc03..a32da410 100644 --- a/scaleway-async/scaleway_async/webhosting/v1/api.py +++ b/scaleway-async/scaleway_async/webhosting/v1/api.py @@ -36,6 +36,7 @@ DnsApiCheckUserOwnsDomainRequest, DnsApiSyncDomainDnsRecordsRequest, DnsRecords, + Domain, FtpAccount, FtpAccountApiChangeFtpAccountPasswordRequest, FtpAccountApiCreateFtpAccountRequest, @@ -59,11 +60,13 @@ OfferOptionRequest, ResetHostingPasswordResponse, ResourceSummary, + SearchDomainsResponse, Session, SyncDomainDnsRecordsRequestRecord, Website, ) from .content import ( + DOMAIN_TRANSIENT_STATUSES, HOSTING_TRANSIENT_STATUSES, ) from .marshalling import ( @@ -73,6 +76,7 @@ unmarshal_MailAccount, unmarshal_CheckUserOwnsDomainResponse, unmarshal_DnsRecords, + unmarshal_Domain, unmarshal_Hosting, unmarshal_ListControlPanelsResponse, unmarshal_ListDatabaseUsersResponse, @@ -84,6 +88,7 @@ unmarshal_ListWebsitesResponse, unmarshal_ResetHostingPasswordResponse, unmarshal_ResourceSummary, + unmarshal_SearchDomainsResponse, unmarshal_Session, marshal_DatabaseApiAssignDatabaseUserRequest, marshal_DatabaseApiChangeDatabaseUserPasswordRequest, @@ -792,7 +797,7 @@ async def check_user_owns_domain( project_id: Optional[str] = None, ) -> CheckUserOwnsDomainResponse: """ - "Check whether you own this domain or not.". + Check whether you own this domain or not. :param domain: Domain for which ownership is to be verified. :param region: Region to target. If none is passed will use default region from the config. :param project_id: ID of the project currently in use. @@ -834,15 +839,17 @@ async def sync_domain_dns_records( update_web_records: bool, update_mail_records: bool, update_all_records: bool, + update_nameservers: bool, region: Optional[ScwRegion] = None, custom_records: Optional[List[SyncDomainDnsRecordsRequestRecord]] = None, ) -> DnsRecords: """ - "Synchronize your DNS records on the Elements Console and on cPanel.". + Synchronize your DNS records on the Elements Console and on cPanel. :param domain: Domain for which the DNS records will be synchronized. :param update_web_records: Whether or not to synchronize the web records. :param update_mail_records: Whether or not to synchronize the mail records. :param update_all_records: Whether or not to synchronize all types of records. This one has priority. + :param update_nameservers: Whether or not to synchronize domain nameservers. :param region: Region to target. If none is passed will use default region from the config. :param custom_records: Custom records to synchronize. :return: :class:`DnsRecords ` @@ -855,6 +862,7 @@ async def sync_domain_dns_records( update_web_records=False, update_mail_records=False, update_all_records=False, + update_nameservers=False, ) """ @@ -872,6 +880,7 @@ async def sync_domain_dns_records( update_web_records=update_web_records, update_mail_records=update_mail_records, update_all_records=update_all_records, + update_nameservers=update_nameservers, region=region, custom_records=custom_records, ), @@ -882,6 +891,121 @@ async def sync_domain_dns_records( self._throw_on_error(res) return unmarshal_DnsRecords(res.json()) + async def search_domains( + self, + *, + domain_name: str, + region: Optional[ScwRegion] = None, + project_id: Optional[str] = None, + ) -> SearchDomainsResponse: + """ + Search for available domains based on domain name. + :param domain_name: Domain name to search. + :param region: Region to target. If none is passed will use default region from the config. + :param project_id: ID of the Scaleway Project in which to search the domain to create the Web Hosting plan. + :return: :class:`SearchDomainsResponse ` + + Usage: + :: + + result = await api.search_domains( + domain_name="example", + ) + """ + + param_region = validate_path_param( + "region", region or self.client.default_region + ) + + res = self._request( + "GET", + f"/webhosting/v1/regions/{param_region}/search-domains", + params={ + "domain_name": domain_name, + "project_id": project_id or self.client.default_project_id, + }, + ) + + self._throw_on_error(res) + return unmarshal_SearchDomainsResponse(res.json()) + + async def get_domain( + self, + *, + domain_name: str, + region: Optional[ScwRegion] = None, + project_id: Optional[str] = None, + ) -> Domain: + """ + Retrieve detailed information about a specific domain, including its status, DNS configuration, and ownership. + :param domain_name: Domain name to get. + :param region: Region to target. If none is passed will use default region from the config. + :param project_id: ID of the Scaleway Project in which to get the domain to create the Web Hosting plan. + :return: :class:`Domain ` + + Usage: + :: + + result = await api.get_domain( + domain_name="example", + ) + """ + + param_region = validate_path_param( + "region", region or self.client.default_region + ) + param_domain_name = validate_path_param("domain_name", domain_name) + + res = self._request( + "GET", + f"/webhosting/v1/regions/{param_region}/domains/{param_domain_name}", + params={ + "project_id": project_id or self.client.default_project_id, + }, + ) + + self._throw_on_error(res) + return unmarshal_Domain(res.json()) + + async def wait_for_domain( + self, + *, + domain_name: str, + region: Optional[ScwRegion] = None, + project_id: Optional[str] = None, + options: Optional[WaitForOptions[Domain, Union[bool, Awaitable[bool]]]] = None, + ) -> Domain: + """ + Retrieve detailed information about a specific domain, including its status, DNS configuration, and ownership. + :param domain_name: Domain name to get. + :param region: Region to target. If none is passed will use default region from the config. + :param project_id: ID of the Scaleway Project in which to get the domain to create the Web Hosting plan. + :return: :class:`Domain ` + + Usage: + :: + + result = await api.get_domain( + domain_name="example", + ) + """ + + if not options: + options = WaitForOptions() + + if not options.stop: + options.stop = lambda res: res.status not in DOMAIN_TRANSIENT_STATUSES + + return await wait_for_resource_async( + fetcher=self.get_domain, + options=options, + args={ + "domain_name": domain_name, + "region": region, + "project_id": project_id, + }, + ) + class WebhostingV1OfferAPI(API): """ diff --git a/scaleway-async/scaleway_async/webhosting/v1/content.py b/scaleway-async/scaleway_async/webhosting/v1/content.py index 0d24b738..7c429cec 100644 --- a/scaleway-async/scaleway_async/webhosting/v1/content.py +++ b/scaleway-async/scaleway_async/webhosting/v1/content.py @@ -3,10 +3,24 @@ from typing import List from .types import ( + DomainAvailabilityStatus, + DomainStatus, HostingStatus, HostingSummaryStatus, ) +DOMAIN_AVAILABILITY_TRANSIENT_STATUSES: List[DomainAvailabilityStatus] = [ + DomainAvailabilityStatus.VALIDATING, +] +""" +Lists transient statutes of the enum :class:`DomainAvailabilityStatus `. +""" +DOMAIN_TRANSIENT_STATUSES: List[DomainStatus] = [ + DomainStatus.VALIDATING, +] +""" +Lists transient statutes of the enum :class:`DomainStatus `. +""" HOSTING_TRANSIENT_STATUSES: List[HostingStatus] = [ HostingStatus.DELIVERING, HostingStatus.DELETING, diff --git a/scaleway-async/scaleway_async/webhosting/v1/marshalling.py b/scaleway-async/scaleway_async/webhosting/v1/marshalling.py index 6a43cdaa..462a8ccb 100644 --- a/scaleway-async/scaleway_async/webhosting/v1/marshalling.py +++ b/scaleway-async/scaleway_async/webhosting/v1/marshalling.py @@ -13,6 +13,9 @@ resolve_one_of, ) from .types import ( + DomainAction, + DomainAvailabilityAction, + DomainDnsAction, DatabaseUser, Database, FtpAccount, @@ -21,6 +24,7 @@ DnsRecord, Nameserver, DnsRecords, + Domain, PlatformControlPanelUrls, OfferOption, PlatformControlPanel, @@ -41,6 +45,8 @@ ListWebsitesResponse, ResetHostingPasswordResponse, ResourceSummary, + DomainAvailability, + SearchDomainsResponse, Session, DatabaseApiAssignDatabaseUserRequest, DatabaseApiChangeDatabaseUserPasswordRequest, @@ -248,6 +254,41 @@ def unmarshal_DnsRecords(data: Any) -> DnsRecords: return DnsRecords(**args) +def unmarshal_Domain(data: Any) -> Domain: + if not isinstance(data, dict): + raise TypeError( + "Unmarshalling the type 'Domain' failed as data isn't a dictionary." + ) + + args: Dict[str, Any] = {} + + field = data.get("name", None) + if field is not None: + args["name"] = field + + field = data.get("status", None) + if field is not None: + args["status"] = field + + field = data.get("owner", None) + if field is not None: + args["owner"] = field + + field = data.get("available_actions", None) + if field is not None: + args["available_actions"] = ( + [DomainAction(v) for v in field] if field is not None else None + ) + + field = data.get("available_dns_actions", None) + if field is not None: + args["available_dns_actions"] = ( + [DomainDnsAction(v) for v in field] if field is not None else None + ) + + return Domain(**args) + + def unmarshal_PlatformControlPanelUrls(data: Any) -> PlatformControlPanelUrls: if not isinstance(data, dict): raise TypeError( @@ -840,6 +881,64 @@ def unmarshal_ResourceSummary(data: Any) -> ResourceSummary: return ResourceSummary(**args) +def unmarshal_DomainAvailability(data: Any) -> DomainAvailability: + if not isinstance(data, dict): + raise TypeError( + "Unmarshalling the type 'DomainAvailability' failed as data isn't a dictionary." + ) + + args: Dict[str, Any] = {} + + field = data.get("name", None) + if field is not None: + args["name"] = field + + field = data.get("zone_name", None) + if field is not None: + args["zone_name"] = field + + field = data.get("status", None) + if field is not None: + args["status"] = field + + field = data.get("available_actions", None) + if field is not None: + args["available_actions"] = ( + [DomainAvailabilityAction(v) for v in field] if field is not None else None + ) + + field = data.get("can_create_hosting", None) + if field is not None: + args["can_create_hosting"] = field + + field = data.get("price", None) + if field is not None: + args["price"] = unmarshal_Money(field) + else: + args["price"] = None + + return DomainAvailability(**args) + + +def unmarshal_SearchDomainsResponse(data: Any) -> SearchDomainsResponse: + if not isinstance(data, dict): + raise TypeError( + "Unmarshalling the type 'SearchDomainsResponse' failed as data isn't a dictionary." + ) + + args: Dict[str, Any] = {} + + field = data.get("domains_available", None) + if field is not None: + args["domains_available"] = ( + [unmarshal_DomainAvailability(v) for v in field] + if field is not None + else None + ) + + return SearchDomainsResponse(**args) + + def unmarshal_Session(data: Any) -> Session: if not isinstance(data, dict): raise TypeError( @@ -983,6 +1082,9 @@ def marshal_DnsApiSyncDomainDnsRecordsRequest( if request.update_all_records is not None: output["update_all_records"] = request.update_all_records + if request.update_nameservers is not None: + output["update_nameservers"] = request.update_nameservers + if request.custom_records is not None: output["custom_records"] = [ marshal_SyncDomainDnsRecordsRequestRecord(item, defaults) diff --git a/scaleway-async/scaleway_async/webhosting/v1/types.py b/scaleway-async/scaleway_async/webhosting/v1/types.py index 3b3852f9..ee610795 100644 --- a/scaleway-async/scaleway_async/webhosting/v1/types.py +++ b/scaleway-async/scaleway_async/webhosting/v1/types.py @@ -51,6 +51,71 @@ def __str__(self) -> str: return str(self.value) +class DomainAction(str, Enum, metaclass=StrEnumMeta): + UNKNOWN_ACTION = "unknown_action" + TRANSFER = "transfer" + MANAGE_EXTERNAL = "manage_external" + RENEW = "renew" + + def __str__(self) -> str: + return str(self.value) + + +class DomainAvailabilityAction(str, Enum, metaclass=StrEnumMeta): + UNKNOWN_ACTION = "unknown_action" + REGISTER = "register" + TRANSFER = "transfer" + MANAGE_EXTERNAL = "manage_external" + + def __str__(self) -> str: + return str(self.value) + + +class DomainAvailabilityStatus(str, Enum, metaclass=StrEnumMeta): + UNKNOWN_STATUS = "unknown_status" + AVAILABLE = "available" + NOT_AVAILABLE = "not_available" + OWNED = "owned" + VALIDATING = "validating" + ERROR = "error" + + def __str__(self) -> str: + return str(self.value) + + +class DomainDnsAction(str, Enum, metaclass=StrEnumMeta): + UNKNOWN_DNS_ACTION = "unknown_dns_action" + AUTO_CONFIG_ALL_RECORDS = "auto_config_all_records" + AUTO_CONFIG_WEB_RECORDS = "auto_config_web_records" + AUTO_CONFIG_MAIL_RECORDS = "auto_config_mail_records" + AUTO_CONFIG_NAMESERVERS = "auto_config_nameservers" + + def __str__(self) -> str: + return str(self.value) + + +class DomainStatus(str, Enum, metaclass=StrEnumMeta): + UNKNOWN_STATUS = "unknown_status" + VALID = "valid" + INVALID = "invalid" + VALIDATING = "validating" + ERROR = "error" + + def __str__(self) -> str: + return str(self.value) + + +class DomainZoneOwner(str, Enum, metaclass=StrEnumMeta): + UNKNOWN_ZONE_OWNER = "unknown_zone_owner" + EXTERNAL = "external" + SCALEWAY = "scaleway" + ONLINE = "online" + WEBHOSTING = "webhosting" + + def __str__(self) -> str: + return str(self.value) + + class HostingStatus(str, Enum, metaclass=StrEnumMeta): UNKNOWN_STATUS = "unknown_status" DELIVERING = "delivering" @@ -586,6 +651,39 @@ class Website: """ +@dataclass +class DomainAvailability: + name: str + """ + Fully qualified domain name (FQDN). + """ + + zone_name: str + """ + DNS zone associated with the domain. + """ + + status: DomainAvailabilityStatus + """ + Availability status of the domain. + """ + + available_actions: List[DomainAvailabilityAction] + """ + A list of actions that can be performed on the domain. + """ + + can_create_hosting: bool + """ + Whether a hosting can be created for this domain. + """ + + price: Optional[Money] + """ + Price for registering the domain. + """ + + @dataclass class CheckUserOwnsDomainResponse: owns_domain: bool @@ -885,6 +983,42 @@ class DnsApiGetDomainDnsRecordsRequest: """ +@dataclass +class DnsApiGetDomainRequest: + domain_name: str + """ + Domain name to get. + """ + + region: Optional[ScwRegion] + """ + Region to target. If none is passed will use default region from the config. + """ + + project_id: Optional[str] + """ + ID of the Scaleway Project in which to get the domain to create the Web Hosting plan. + """ + + +@dataclass +class DnsApiSearchDomainsRequest: + domain_name: str + """ + Domain name to search. + """ + + region: Optional[ScwRegion] + """ + Region to target. If none is passed will use default region from the config. + """ + + project_id: Optional[str] + """ + ID of the Scaleway Project in which to search the domain to create the Web Hosting plan. + """ + + @dataclass class DnsApiSyncDomainDnsRecordsRequest: domain: str @@ -907,6 +1041,11 @@ class DnsApiSyncDomainDnsRecordsRequest: Whether or not to synchronize all types of records. This one has priority. """ + update_nameservers: bool + """ + Whether or not to synchronize domain nameservers. + """ + region: Optional[ScwRegion] """ Region to target. If none is passed will use default region from the config. @@ -936,6 +1075,34 @@ class DnsRecords: """ +@dataclass +class Domain: + name: str + """ + Name of the domain. + """ + + status: DomainStatus + """ + Current status of the domain. + """ + + owner: DomainZoneOwner + """ + Zone owner of the domain. + """ + + available_actions: List[DomainAction] + """ + A list of actions that can be performed on the domain. + """ + + available_dns_actions: List[DomainDnsAction] + """ + A list of DNS-related actions that can be auto configured for the domain. + """ + + @dataclass class FtpAccountApiChangeFtpAccountPasswordRequest: hosting_id: str @@ -1600,6 +1767,14 @@ class ResourceSummary: """ +@dataclass +class SearchDomainsResponse: + domains_available: List[DomainAvailability] + """ + List of domains availability. + """ + + @dataclass class Session: url: str diff --git a/scaleway-async/scaleway_async/webhosting/v1alpha1/api.py b/scaleway-async/scaleway_async/webhosting/v1alpha1/api.py index 250e37b0..c9d4de46 100644 --- a/scaleway-async/scaleway_async/webhosting/v1alpha1/api.py +++ b/scaleway-async/scaleway_async/webhosting/v1alpha1/api.py @@ -499,7 +499,7 @@ async def check_user_owns_domain( res = self._request( "POST", - f"/webhosting/v1/regions/{param_region}/domains/{param_domain}/check-ownership", + f"/webhosting/v1alpha1/regions/{param_region}/domains/{param_domain}/check-ownership", body=marshal_CheckUserOwnsDomainRequest( CheckUserOwnsDomainRequest( domain=domain, diff --git a/scaleway/scaleway/webhosting/v1/__init__.py b/scaleway/scaleway/webhosting/v1/__init__.py index 0ae2a234..467720f2 100644 --- a/scaleway/scaleway/webhosting/v1/__init__.py +++ b/scaleway/scaleway/webhosting/v1/__init__.py @@ -3,6 +3,14 @@ from .types import DnsRecordStatus from .types import DnsRecordType from .types import DnsRecordsStatus +from .types import DomainAction +from .types import DomainAvailabilityAction +from .types import DomainAvailabilityStatus +from .content import DOMAIN_AVAILABILITY_TRANSIENT_STATUSES +from .types import DomainDnsAction +from .types import DomainStatus +from .content import DOMAIN_TRANSIENT_STATUSES +from .types import DomainZoneOwner from .types import HostingStatus from .content import HOSTING_TRANSIENT_STATUSES from .types import HostingSummaryStatus @@ -37,6 +45,7 @@ from .types import HostingSummary from .types import MailAccount from .types import Website +from .types import DomainAvailability from .types import CheckUserOwnsDomainResponse from .types import ControlPanelApiListControlPanelsRequest from .types import DatabaseApiAssignDatabaseUserRequest @@ -52,8 +61,11 @@ from .types import DatabaseApiUnassignDatabaseUserRequest from .types import DnsApiCheckUserOwnsDomainRequest from .types import DnsApiGetDomainDnsRecordsRequest +from .types import DnsApiGetDomainRequest +from .types import DnsApiSearchDomainsRequest from .types import DnsApiSyncDomainDnsRecordsRequest from .types import DnsRecords +from .types import Domain from .types import FtpAccountApiChangeFtpAccountPasswordRequest from .types import FtpAccountApiCreateFtpAccountRequest from .types import FtpAccountApiListFtpAccountsRequest @@ -82,6 +94,7 @@ from .types import OfferApiListOffersRequest from .types import ResetHostingPasswordResponse from .types import ResourceSummary +from .types import SearchDomainsResponse from .types import Session from .types import WebsiteApiListWebsitesRequest from .api import WebhostingV1ControlPanelAPI @@ -97,6 +110,14 @@ "DnsRecordStatus", "DnsRecordType", "DnsRecordsStatus", + "DomainAction", + "DomainAvailabilityAction", + "DomainAvailabilityStatus", + "DOMAIN_AVAILABILITY_TRANSIENT_STATUSES", + "DomainDnsAction", + "DomainStatus", + "DOMAIN_TRANSIENT_STATUSES", + "DomainZoneOwner", "HostingStatus", "HOSTING_TRANSIENT_STATUSES", "HostingSummaryStatus", @@ -131,6 +152,7 @@ "HostingSummary", "MailAccount", "Website", + "DomainAvailability", "CheckUserOwnsDomainResponse", "ControlPanelApiListControlPanelsRequest", "DatabaseApiAssignDatabaseUserRequest", @@ -146,8 +168,11 @@ "DatabaseApiUnassignDatabaseUserRequest", "DnsApiCheckUserOwnsDomainRequest", "DnsApiGetDomainDnsRecordsRequest", + "DnsApiGetDomainRequest", + "DnsApiSearchDomainsRequest", "DnsApiSyncDomainDnsRecordsRequest", "DnsRecords", + "Domain", "FtpAccountApiChangeFtpAccountPasswordRequest", "FtpAccountApiCreateFtpAccountRequest", "FtpAccountApiListFtpAccountsRequest", @@ -176,6 +201,7 @@ "OfferApiListOffersRequest", "ResetHostingPasswordResponse", "ResourceSummary", + "SearchDomainsResponse", "Session", "WebsiteApiListWebsitesRequest", "WebhostingV1ControlPanelAPI", diff --git a/scaleway/scaleway/webhosting/v1/api.py b/scaleway/scaleway/webhosting/v1/api.py index 486d7792..d613fc2a 100644 --- a/scaleway/scaleway/webhosting/v1/api.py +++ b/scaleway/scaleway/webhosting/v1/api.py @@ -36,6 +36,7 @@ DnsApiCheckUserOwnsDomainRequest, DnsApiSyncDomainDnsRecordsRequest, DnsRecords, + Domain, FtpAccount, FtpAccountApiChangeFtpAccountPasswordRequest, FtpAccountApiCreateFtpAccountRequest, @@ -59,11 +60,13 @@ OfferOptionRequest, ResetHostingPasswordResponse, ResourceSummary, + SearchDomainsResponse, Session, SyncDomainDnsRecordsRequestRecord, Website, ) from .content import ( + DOMAIN_TRANSIENT_STATUSES, HOSTING_TRANSIENT_STATUSES, ) from .marshalling import ( @@ -73,6 +76,7 @@ unmarshal_MailAccount, unmarshal_CheckUserOwnsDomainResponse, unmarshal_DnsRecords, + unmarshal_Domain, unmarshal_Hosting, unmarshal_ListControlPanelsResponse, unmarshal_ListDatabaseUsersResponse, @@ -84,6 +88,7 @@ unmarshal_ListWebsitesResponse, unmarshal_ResetHostingPasswordResponse, unmarshal_ResourceSummary, + unmarshal_SearchDomainsResponse, unmarshal_Session, marshal_DatabaseApiAssignDatabaseUserRequest, marshal_DatabaseApiChangeDatabaseUserPasswordRequest, @@ -792,7 +797,7 @@ def check_user_owns_domain( project_id: Optional[str] = None, ) -> CheckUserOwnsDomainResponse: """ - "Check whether you own this domain or not.". + Check whether you own this domain or not. :param domain: Domain for which ownership is to be verified. :param region: Region to target. If none is passed will use default region from the config. :param project_id: ID of the project currently in use. @@ -834,15 +839,17 @@ def sync_domain_dns_records( update_web_records: bool, update_mail_records: bool, update_all_records: bool, + update_nameservers: bool, region: Optional[ScwRegion] = None, custom_records: Optional[List[SyncDomainDnsRecordsRequestRecord]] = None, ) -> DnsRecords: """ - "Synchronize your DNS records on the Elements Console and on cPanel.". + Synchronize your DNS records on the Elements Console and on cPanel. :param domain: Domain for which the DNS records will be synchronized. :param update_web_records: Whether or not to synchronize the web records. :param update_mail_records: Whether or not to synchronize the mail records. :param update_all_records: Whether or not to synchronize all types of records. This one has priority. + :param update_nameservers: Whether or not to synchronize domain nameservers. :param region: Region to target. If none is passed will use default region from the config. :param custom_records: Custom records to synchronize. :return: :class:`DnsRecords ` @@ -855,6 +862,7 @@ def sync_domain_dns_records( update_web_records=False, update_mail_records=False, update_all_records=False, + update_nameservers=False, ) """ @@ -872,6 +880,7 @@ def sync_domain_dns_records( update_web_records=update_web_records, update_mail_records=update_mail_records, update_all_records=update_all_records, + update_nameservers=update_nameservers, region=region, custom_records=custom_records, ), @@ -882,6 +891,121 @@ def sync_domain_dns_records( self._throw_on_error(res) return unmarshal_DnsRecords(res.json()) + def search_domains( + self, + *, + domain_name: str, + region: Optional[ScwRegion] = None, + project_id: Optional[str] = None, + ) -> SearchDomainsResponse: + """ + Search for available domains based on domain name. + :param domain_name: Domain name to search. + :param region: Region to target. If none is passed will use default region from the config. + :param project_id: ID of the Scaleway Project in which to search the domain to create the Web Hosting plan. + :return: :class:`SearchDomainsResponse ` + + Usage: + :: + + result = api.search_domains( + domain_name="example", + ) + """ + + param_region = validate_path_param( + "region", region or self.client.default_region + ) + + res = self._request( + "GET", + f"/webhosting/v1/regions/{param_region}/search-domains", + params={ + "domain_name": domain_name, + "project_id": project_id or self.client.default_project_id, + }, + ) + + self._throw_on_error(res) + return unmarshal_SearchDomainsResponse(res.json()) + + def get_domain( + self, + *, + domain_name: str, + region: Optional[ScwRegion] = None, + project_id: Optional[str] = None, + ) -> Domain: + """ + Retrieve detailed information about a specific domain, including its status, DNS configuration, and ownership. + :param domain_name: Domain name to get. + :param region: Region to target. If none is passed will use default region from the config. + :param project_id: ID of the Scaleway Project in which to get the domain to create the Web Hosting plan. + :return: :class:`Domain ` + + Usage: + :: + + result = api.get_domain( + domain_name="example", + ) + """ + + param_region = validate_path_param( + "region", region or self.client.default_region + ) + param_domain_name = validate_path_param("domain_name", domain_name) + + res = self._request( + "GET", + f"/webhosting/v1/regions/{param_region}/domains/{param_domain_name}", + params={ + "project_id": project_id or self.client.default_project_id, + }, + ) + + self._throw_on_error(res) + return unmarshal_Domain(res.json()) + + def wait_for_domain( + self, + *, + domain_name: str, + region: Optional[ScwRegion] = None, + project_id: Optional[str] = None, + options: Optional[WaitForOptions[Domain, bool]] = None, + ) -> Domain: + """ + Retrieve detailed information about a specific domain, including its status, DNS configuration, and ownership. + :param domain_name: Domain name to get. + :param region: Region to target. If none is passed will use default region from the config. + :param project_id: ID of the Scaleway Project in which to get the domain to create the Web Hosting plan. + :return: :class:`Domain ` + + Usage: + :: + + result = api.get_domain( + domain_name="example", + ) + """ + + if not options: + options = WaitForOptions() + + if not options.stop: + options.stop = lambda res: res.status not in DOMAIN_TRANSIENT_STATUSES + + return wait_for_resource( + fetcher=self.get_domain, + options=options, + args={ + "domain_name": domain_name, + "region": region, + "project_id": project_id, + }, + ) + class WebhostingV1OfferAPI(API): """ diff --git a/scaleway/scaleway/webhosting/v1/content.py b/scaleway/scaleway/webhosting/v1/content.py index 0d24b738..7c429cec 100644 --- a/scaleway/scaleway/webhosting/v1/content.py +++ b/scaleway/scaleway/webhosting/v1/content.py @@ -3,10 +3,24 @@ from typing import List from .types import ( + DomainAvailabilityStatus, + DomainStatus, HostingStatus, HostingSummaryStatus, ) +DOMAIN_AVAILABILITY_TRANSIENT_STATUSES: List[DomainAvailabilityStatus] = [ + DomainAvailabilityStatus.VALIDATING, +] +""" +Lists transient statutes of the enum :class:`DomainAvailabilityStatus `. +""" +DOMAIN_TRANSIENT_STATUSES: List[DomainStatus] = [ + DomainStatus.VALIDATING, +] +""" +Lists transient statutes of the enum :class:`DomainStatus `. +""" HOSTING_TRANSIENT_STATUSES: List[HostingStatus] = [ HostingStatus.DELIVERING, HostingStatus.DELETING, diff --git a/scaleway/scaleway/webhosting/v1/marshalling.py b/scaleway/scaleway/webhosting/v1/marshalling.py index 6a43cdaa..462a8ccb 100644 --- a/scaleway/scaleway/webhosting/v1/marshalling.py +++ b/scaleway/scaleway/webhosting/v1/marshalling.py @@ -13,6 +13,9 @@ resolve_one_of, ) from .types import ( + DomainAction, + DomainAvailabilityAction, + DomainDnsAction, DatabaseUser, Database, FtpAccount, @@ -21,6 +24,7 @@ DnsRecord, Nameserver, DnsRecords, + Domain, PlatformControlPanelUrls, OfferOption, PlatformControlPanel, @@ -41,6 +45,8 @@ ListWebsitesResponse, ResetHostingPasswordResponse, ResourceSummary, + DomainAvailability, + SearchDomainsResponse, Session, DatabaseApiAssignDatabaseUserRequest, DatabaseApiChangeDatabaseUserPasswordRequest, @@ -248,6 +254,41 @@ def unmarshal_DnsRecords(data: Any) -> DnsRecords: return DnsRecords(**args) +def unmarshal_Domain(data: Any) -> Domain: + if not isinstance(data, dict): + raise TypeError( + "Unmarshalling the type 'Domain' failed as data isn't a dictionary." + ) + + args: Dict[str, Any] = {} + + field = data.get("name", None) + if field is not None: + args["name"] = field + + field = data.get("status", None) + if field is not None: + args["status"] = field + + field = data.get("owner", None) + if field is not None: + args["owner"] = field + + field = data.get("available_actions", None) + if field is not None: + args["available_actions"] = ( + [DomainAction(v) for v in field] if field is not None else None + ) + + field = data.get("available_dns_actions", None) + if field is not None: + args["available_dns_actions"] = ( + [DomainDnsAction(v) for v in field] if field is not None else None + ) + + return Domain(**args) + + def unmarshal_PlatformControlPanelUrls(data: Any) -> PlatformControlPanelUrls: if not isinstance(data, dict): raise TypeError( @@ -840,6 +881,64 @@ def unmarshal_ResourceSummary(data: Any) -> ResourceSummary: return ResourceSummary(**args) +def unmarshal_DomainAvailability(data: Any) -> DomainAvailability: + if not isinstance(data, dict): + raise TypeError( + "Unmarshalling the type 'DomainAvailability' failed as data isn't a dictionary." + ) + + args: Dict[str, Any] = {} + + field = data.get("name", None) + if field is not None: + args["name"] = field + + field = data.get("zone_name", None) + if field is not None: + args["zone_name"] = field + + field = data.get("status", None) + if field is not None: + args["status"] = field + + field = data.get("available_actions", None) + if field is not None: + args["available_actions"] = ( + [DomainAvailabilityAction(v) for v in field] if field is not None else None + ) + + field = data.get("can_create_hosting", None) + if field is not None: + args["can_create_hosting"] = field + + field = data.get("price", None) + if field is not None: + args["price"] = unmarshal_Money(field) + else: + args["price"] = None + + return DomainAvailability(**args) + + +def unmarshal_SearchDomainsResponse(data: Any) -> SearchDomainsResponse: + if not isinstance(data, dict): + raise TypeError( + "Unmarshalling the type 'SearchDomainsResponse' failed as data isn't a dictionary." + ) + + args: Dict[str, Any] = {} + + field = data.get("domains_available", None) + if field is not None: + args["domains_available"] = ( + [unmarshal_DomainAvailability(v) for v in field] + if field is not None + else None + ) + + return SearchDomainsResponse(**args) + + def unmarshal_Session(data: Any) -> Session: if not isinstance(data, dict): raise TypeError( @@ -983,6 +1082,9 @@ def marshal_DnsApiSyncDomainDnsRecordsRequest( if request.update_all_records is not None: output["update_all_records"] = request.update_all_records + if request.update_nameservers is not None: + output["update_nameservers"] = request.update_nameservers + if request.custom_records is not None: output["custom_records"] = [ marshal_SyncDomainDnsRecordsRequestRecord(item, defaults) diff --git a/scaleway/scaleway/webhosting/v1/types.py b/scaleway/scaleway/webhosting/v1/types.py index 3b3852f9..ee610795 100644 --- a/scaleway/scaleway/webhosting/v1/types.py +++ b/scaleway/scaleway/webhosting/v1/types.py @@ -51,6 +51,71 @@ def __str__(self) -> str: return str(self.value) +class DomainAction(str, Enum, metaclass=StrEnumMeta): + UNKNOWN_ACTION = "unknown_action" + TRANSFER = "transfer" + MANAGE_EXTERNAL = "manage_external" + RENEW = "renew" + + def __str__(self) -> str: + return str(self.value) + + +class DomainAvailabilityAction(str, Enum, metaclass=StrEnumMeta): + UNKNOWN_ACTION = "unknown_action" + REGISTER = "register" + TRANSFER = "transfer" + MANAGE_EXTERNAL = "manage_external" + + def __str__(self) -> str: + return str(self.value) + + +class DomainAvailabilityStatus(str, Enum, metaclass=StrEnumMeta): + UNKNOWN_STATUS = "unknown_status" + AVAILABLE = "available" + NOT_AVAILABLE = "not_available" + OWNED = "owned" + VALIDATING = "validating" + ERROR = "error" + + def __str__(self) -> str: + return str(self.value) + + +class DomainDnsAction(str, Enum, metaclass=StrEnumMeta): + UNKNOWN_DNS_ACTION = "unknown_dns_action" + AUTO_CONFIG_ALL_RECORDS = "auto_config_all_records" + AUTO_CONFIG_WEB_RECORDS = "auto_config_web_records" + AUTO_CONFIG_MAIL_RECORDS = "auto_config_mail_records" + AUTO_CONFIG_NAMESERVERS = "auto_config_nameservers" + + def __str__(self) -> str: + return str(self.value) + + +class DomainStatus(str, Enum, metaclass=StrEnumMeta): + UNKNOWN_STATUS = "unknown_status" + VALID = "valid" + INVALID = "invalid" + VALIDATING = "validating" + ERROR = "error" + + def __str__(self) -> str: + return str(self.value) + + +class DomainZoneOwner(str, Enum, metaclass=StrEnumMeta): + UNKNOWN_ZONE_OWNER = "unknown_zone_owner" + EXTERNAL = "external" + SCALEWAY = "scaleway" + ONLINE = "online" + WEBHOSTING = "webhosting" + + def __str__(self) -> str: + return str(self.value) + + class HostingStatus(str, Enum, metaclass=StrEnumMeta): UNKNOWN_STATUS = "unknown_status" DELIVERING = "delivering" @@ -586,6 +651,39 @@ class Website: """ +@dataclass +class DomainAvailability: + name: str + """ + Fully qualified domain name (FQDN). + """ + + zone_name: str + """ + DNS zone associated with the domain. + """ + + status: DomainAvailabilityStatus + """ + Availability status of the domain. + """ + + available_actions: List[DomainAvailabilityAction] + """ + A list of actions that can be performed on the domain. + """ + + can_create_hosting: bool + """ + Whether a hosting can be created for this domain. + """ + + price: Optional[Money] + """ + Price for registering the domain. + """ + + @dataclass class CheckUserOwnsDomainResponse: owns_domain: bool @@ -885,6 +983,42 @@ class DnsApiGetDomainDnsRecordsRequest: """ +@dataclass +class DnsApiGetDomainRequest: + domain_name: str + """ + Domain name to get. + """ + + region: Optional[ScwRegion] + """ + Region to target. If none is passed will use default region from the config. + """ + + project_id: Optional[str] + """ + ID of the Scaleway Project in which to get the domain to create the Web Hosting plan. + """ + + +@dataclass +class DnsApiSearchDomainsRequest: + domain_name: str + """ + Domain name to search. + """ + + region: Optional[ScwRegion] + """ + Region to target. If none is passed will use default region from the config. + """ + + project_id: Optional[str] + """ + ID of the Scaleway Project in which to search the domain to create the Web Hosting plan. + """ + + @dataclass class DnsApiSyncDomainDnsRecordsRequest: domain: str @@ -907,6 +1041,11 @@ class DnsApiSyncDomainDnsRecordsRequest: Whether or not to synchronize all types of records. This one has priority. """ + update_nameservers: bool + """ + Whether or not to synchronize domain nameservers. + """ + region: Optional[ScwRegion] """ Region to target. If none is passed will use default region from the config. @@ -936,6 +1075,34 @@ class DnsRecords: """ +@dataclass +class Domain: + name: str + """ + Name of the domain. + """ + + status: DomainStatus + """ + Current status of the domain. + """ + + owner: DomainZoneOwner + """ + Zone owner of the domain. + """ + + available_actions: List[DomainAction] + """ + A list of actions that can be performed on the domain. + """ + + available_dns_actions: List[DomainDnsAction] + """ + A list of DNS-related actions that can be auto configured for the domain. + """ + + @dataclass class FtpAccountApiChangeFtpAccountPasswordRequest: hosting_id: str @@ -1600,6 +1767,14 @@ class ResourceSummary: """ +@dataclass +class SearchDomainsResponse: + domains_available: List[DomainAvailability] + """ + List of domains availability. + """ + + @dataclass class Session: url: str diff --git a/scaleway/scaleway/webhosting/v1alpha1/api.py b/scaleway/scaleway/webhosting/v1alpha1/api.py index c960e44a..afcb9702 100644 --- a/scaleway/scaleway/webhosting/v1alpha1/api.py +++ b/scaleway/scaleway/webhosting/v1alpha1/api.py @@ -499,7 +499,7 @@ def check_user_owns_domain( res = self._request( "POST", - f"/webhosting/v1/regions/{param_region}/domains/{param_domain}/check-ownership", + f"/webhosting/v1alpha1/regions/{param_region}/domains/{param_domain}/check-ownership", body=marshal_CheckUserOwnsDomainRequest( CheckUserOwnsDomainRequest( domain=domain,